enabled(){
  this.isDebugging = false;

  this.onKeyDown = (e) => {
    // ==========================
    // F4 key - toggle debug mode
    // ==========================

    if (e.keyCode === 115){
      this.isDebugging = !this.isDebugging;
      $(".app-title").first().css("background-color", this.isDebugging ? "#5A6B75" : "#292F33");
    }

    // Debug mode handling

    else if (this.isDebugging){
      e.preventDefault();

      // ===================================
      // N key - simulate popup notification
      // S key - simulate sound notification
      // ===================================

      if (e.keyCode === 78 || e.keyCode === 83){
        var col = TD.controller.columnManager.getAllOrdered()[0];

        var prevPopup = col.model.getHasNotification();
        var prevSound = col.model.getHasSound();

        col.model.setHasNotification(e.keyCode === 78);
        col.model.setHasSound(e.keyCode === 83);

        $.publish("/notifications/new",[{
          column: col,
          items: [
            col.updateArray[Math.floor(Math.random()*col.updateArray.length)]
          ]
        }]);

        setTimeout(function(){
          col.model.setHasNotification(prevPopup);
          col.model.setHasSound(prevSound);
        }, 1);
      }
    }
  };
}

ready(){
  $(document).on("keydown", this.onKeyDown);
}

disabled(){
  $(document).off("keydown", this.onKeyDown);
}

enabled(){
  this.isDebugging = false;

  this.onKeyDown = (e) => {
    // ==========================
    // F4 key - toggle debug mode
    // ==========================

    if (e.keyCode === 115){
      this.isDebugging = !this.isDebugging;
      $(".nav-user-info").first().css("background-color", this.isDebugging ? "#5A6B75" : "#292F33");
    }

    // Debug mode handling

    else if (this.isDebugging){
      e.preventDefault();

      // ===================================
      // N key - simulate popup notification
      // S key - simulate sound notification
      // ===================================

      if (e.keyCode === 78 || e.keyCode === 83){
        var col = TD.controller.columnManager.getAllOrdered()[0];

        var prevPopup = col.model.getHasNotification();
        var prevSound = col.model.getHasSound();

        col.model.setHasNotification(e.keyCode === 78);
        col.model.setHasSound(e.keyCode === 83);

        $.publish("/notifications/new",[{
          column: col,
          items: [
            col.updateArray[Math.floor(Math.random()*col.updateArray.length)]
          ]
        }]);

        setTimeout(function(){
          col.model.setHasNotification(prevPopup);
          col.model.setHasSound(prevSound);
        }, 1);
      }
    }
  };
}

ready(){
  $(document).on("keydown", this.onKeyDown);
}

disabled(){
  $(document).off("keydown", this.onKeyDown);
}

Fix debug plugin after hiding app title


var utils       = require('utils')
  , env         = utils.bootstrapEnv()
  , moduleLdr   = env.moduleLoader
  , injector    = require('injector')
  , ncp         = require('ncp')
  , path        = require('path')
  , async       = require('async');

describe('ModuleLoader', function() {

  before(function(done) {
    var source  = path.resolve(__dirname, '..', 'test-module')
      , dest    = path.resolve(__dirname, '..', '..', '..', 'modules', 'test-module');

    // Copy the test-module into the modules folder
    ncp(source, dest, done);
  });

  it('should load modules', function(done) {
    this.timeout(20000);

    moduleLdr.on('modulesLoaded', function() {
      async.parallel(
        [
          function ormDb(callback) {
            if (moduleLdr.moduleIsEnabled('clever-orm') === true) {
              injector
                .getInstance('sequelize')
                .sync({ force: true })
                .then(function() {
                  callback(null);
                })
                .catch(callback);
            } else {
              callback(null);
            }
          },

          function odmDb(callback) {
            callback(null);
          }
        ],
        function(err) {
          if (err !== undefined && err !== null) {
            console.dir(err);
            return done(err);
          }
          done();
        }
     );
    });
    moduleLdr.loadModules();
  });

  it('should initialize all module routes', function(done) {
    moduleLdr.on('routesInitialized', function() {
      done();
    });
    moduleLdr.initializeRoutes();
  });
});

var utils       = require('utils')
  , env         = utils.bootstrapEnv()
  , moduleLdr   = env.moduleLoader
  , injector    = require('injector')
  , ncp         = require('ncp')
  , path        = require('path')
  , async       = require('async');

describe('ModuleLoader', function() {

  it('should load modules', function(done) {
    this.timeout(20000);

    moduleLdr.on('modulesLoaded', function() {
      async.parallel(
        [
          function ormDb(callback) {
            if (moduleLdr.moduleIsEnabled('clever-orm') === true) {
              injector
                .getInstance('sequelize')
                .sync({ force: true })
                .then(function() {
                  callback(null);
                })
                .catch(callback);
            } else {
              callback(null);
            }
          },

          function odmDb(callback) {
            callback(null);
          }
        ],
        function(err) {
          if (err !== undefined && err !== null) {
            console.dir(err);
            return done(err);
          }
          done();
        }
     );
    });
    moduleLdr.loadModules();
  });

  it('should initialize all module routes', function(done) {
    moduleLdr.on('routesInitialized', function() {
      done();
    });
    moduleLdr.initializeRoutes();
  });
});

Remove old code for copying test-module


/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */
/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */

const canUseDOM = !!(
	typeof window !== 'undefined' &&
	window.document &&
	window.document.createElement
);

const ExecutionEnvironment = {
	canUseDOM,
	canUseWorkers: typeof Worker !== 'undefined',
	canUseEventListeners:
		canUseDOM && !!(window.addEventListener || window.attachEvent),
	canUseViewport: canUseDOM && !!window.screen,
};

export default ExecutionEnvironment;

/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */
/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */

const canUseDOM = !!(
	typeof window !== 'undefined' &&
	window.document &&
	window.document.createElement
);
const canUseWorkers = typeof Worker !== 'undefined';
const canUseEventListeners = canUseDOM && !!(window.addEventListener || window.attachEvent);
const canUseViewport = canUseDOM && !!window.screen;

export {
	canUseDOM,
	canUseWorkers,
	canUseEventListeners,
	canUseViewport,
};

Change exports for Execution Environment


'use strict';

var Semvish = require('semvish');

module.exports = function(arr) {
    if(!arr) {
        return;
    }

    return arr.slice().sort(Semvish.compare).reverse();
};

'use strict';

var Semvish = require('semvish');

module.exports = function(arr) {
    if(!arr) {
        return;
    }

    return arr.slice().sort(Semvish.rcompare);
};

Use rcomp instead of .reverse() in sort


require.config({
    paths: {
        jquery: "/static/js/libs/jquery-min",
        underscore: "/static/js/libs/underscore-min",
        backbone: "/static/js/libs/backbone-min",
        mustache: "/static/js/libs/mustache",
    },
    shim: {
        jquery: {
            exports: "$"
        },
        underscore: {
            exports: "_"
        },
        backbone: {
            deps: ["underscore", "jquery"],
            exports: "Backbone"
        }
    }
});

require(["jquery", "underscore","backbone", "mustache"],
function ($, _, Backbone, Mustache) {
$(function () {

// the search bar
var SearchBar = Backbone.Model.extend({
    initialize: function () {
        this.bind("change:position", function () {
            // TODO: change CSS class
            console.log("position changed!");
        });
    },
    defaults: {
        position: "center" // the current location of the search bar/box
    }
});

var SearchBarView = Backbone.View.extend({
    id: "search-bar",
    className: "search-bar-position-center"
});

// a suggestion below the search bar
var AutocompleteSuggestion = Backbone.Model.extend({
    defaults: {
        originator: null,
        text: ""
    }
});

var AutocompleteSuggestionCollection = Backbone.Collection.extend({
    model: AutocompleteSuggestion
});

// ** END **
});
});

require.config({
    paths: {
        jquery: "/static/js/libs/jquery-min",
        underscore: "/static/js/libs/underscore-min",
        backbone: "/static/js/libs/backbone-min",
        mustache: "/static/js/libs/mustache",
    },
    shim: {
        jquery: {
            exports: "$"
        },
        underscore: {
            exports: "_"
        },
        backbone: {
            deps: ["underscore", "jquery"],
            exports: "Backbone"
        }
    },

    // add dummy params to break caching
    urlArgs: "__nocache__=" +  (new Date()).getTime()
});

require(["jquery", "underscore","backbone", "mustache"],
function ($, _, Backbone, Mustache) {
$(function () {

// the search bar
var SearchBar = Backbone.Model.extend({
    initialize: function () {
        this.bind("change:position", function () {
            // TODO: change CSS class
            console.log("position changed!");
        });
    },
    defaults: {
        position: "center" // the current location of the search bar/box
    }
});

var SearchBarView = Backbone.View.extend({
    id: "search-bar",
    className: "search-bar-position-center"
});

// a suggestion below the search bar
var AutocompleteSuggestion = Backbone.Model.extend({
    defaults: {
        originator: null,
        text: ""
    }
});

var AutocompleteSuggestionCollection = Backbone.Collection.extend({
    model: AutocompleteSuggestion
});

// ** END **
});
});

Add dummy param to break caching


//var Teachers = require('../models/teachers');
//var TeacherClasses = require('../models/Teacher_classes');
//var Classes = require('../models/classes');
//var ClassLessons = require('../models/class_lessons');
// var Lessons = require('../models/lessons');
//var RequestedResponses = require('../models/requested_responses');

module.exports = {

  readyStage : function(io, req, res, next) {

    //var studentInformation = req.body.studentData
    var pollResponse = {
      responseId: 1,
      type: 'thumbs',
      datetime: new Date(),
      lessonId: 13,
    };

    io.on('connection', function(student){

      student.emit('studentStandby', studentInformation);

      student.on('teacherConnect', function() {
        student.emit('teacherConnect');
      });

      student.on('newPoll', function(data) {
        student.emit(data);
      });

      setTimeout(function(){
        io.sockets.emit('responseFromStudent', pollResponse);
      }, 5000);

    });
    res.status(200).send('Hello from the other side');
  }
};

//var Teachers = require('../models/teachers');
//var TeacherClasses = require('../models/Teacher_classes');
//var Classes = require('../models/classes');
//var ClassLessons = require('../models/class_lessons');
// var Lessons = require('../models/lessons');
//var RequestedResponses = require('../models/requested_responses');

module.exports = {

  readyStage : function(io, req, res, next) {

    //var studentInformation = req.body.studentData
    var pollResponse = {
      responseId: 1,
      type: 'thumbs',
      datetime: new Date(),
      lessonId: 13,
    };

    io.on('connection', function(student){

      student.emit('studentStandby', studentInformation);

      student.on('newPoll', function(data) {
        student.emit(data);
      });

      setTimeout(function(){
        io.sockets.emit('responseFromStudent', pollResponse);
      }, 5000);

    });
    res.status(200).send('Hello from the student side');
  }
};

Remove teacherConnect socket event for frontend handling


/**
 * @copyright Copyright (c) 2012-2015 2amigOS! Consulting Group LLC
 * @link http://2amigos.us
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
 */
if (typeof dosamigos == "undefined" || !dosamigos) {
    var dosamigos = {};
}

dosamigos.ckEditorWidget = (function ($) {

    var pub = {
        registerOnChangeHandler: function (id) {
            CKEDITOR && CKEDITOR.instances[id] && CKEDITOR.instances[id].on('change', function () {
                CKEDITOR.instances[id].updateElement();
                $('#' + id).trigger('change');
                return false;
            });
        },
        registerCsrfImageUploadHandler: function () {
            yii & $(document).off('click', '.cke_dialog_tabs a:eq(2)').on('click', '.cke_dialog_tabs a:eq(2)', function () {
                var $form = $('.cke_dialog_ui_input_file iframe').contents().find('form');
                var csrfName = yii.getCsrfParam();
                if (!$form.find('input[name=' + csrfName + ']').length) {
                    var csrfTokenInput = $('<input/>').attr({
                        'type': 'hidden',
                        'name': csrfName
                    }).val(yii.getCsrfToken());
                    $form.append(csrfTokenInput);
                }
            });
        }
    };
    return pub;
})(jQuery);

/**
 * @copyright Copyright (c) 2012-2015 2amigOS! Consulting Group LLC
 * @link http://2amigos.us
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
 */
if (typeof dosamigos == "undefined" || !dosamigos) {
    var dosamigos = {};
}

dosamigos.ckEditorWidget = (function ($) {

    var pub = {
        registerOnChangeHandler: function (id) {
            CKEDITOR && CKEDITOR.instances[id] && CKEDITOR.instances[id].on('change', function () {
                CKEDITOR.instances[id].updateElement();
                $('#' + id).trigger('change');
                return false;
            });
        },
        registerCsrfImageUploadHandler: function () {
            yii & $(document).off('click', '.cke_dialog_tabs a:eq(1), .cke_dialog_tabs a:eq(2)').on('click', '.cke_dialog_tabs a:eq(1), .cke_dialog_tabs a:eq(2)', function () {
                var $form = $('.cke_dialog_ui_input_file iframe').contents().find('form');
                var csrfName = yii.getCsrfParam();
                if (!$form.find('input[name=' + csrfName + ']').length) {
                    var csrfTokenInput = $('<input/>').attr({
                        'type': 'hidden',
                        'name': csrfName
                    }).val(yii.getCsrfToken());
                    $form.append(csrfTokenInput);
                }
            });
        }
    };
    return pub;
})(jQuery);

Fix bug with csrf token


var m = angular.module("routeUrls", []);

m.factory("urls", function($route) {

    var pathsByName = {};
    angular.forEach($route.routes, function (route, path) {
        if (route.name) {
            pathsByName[route.name] = path;
        }
    });

    var regexs = {};

    var path = function (name, params) {
        var url = pathsByName[name] || "/";
        angular.forEach(params || {}, function (value, key) {
            var regex = regexs[key];

            if (regex === undefined) {
              regex = regexs[key] = new RegExp(":" + key + "(/|$)");
            }
            url = url.replace(regex, value);
        });
        return url;
    };

    return {
        path: path,

        href: function (name, params) {
            return "#" + path(name, params);
        }
    };
});

var m = angular.module("routeUrls", []);

m.factory("urls", function($route) {

    var pathsByName = {};
    angular.forEach($route.routes, function (route, path) {
        if (route.name) {
            pathsByName[route.name] = path;
        }
    });

    var regexs = {};

    var path = function (name, params) {
        var url = pathsByName[name] || "/";
        angular.forEach(params || {}, function (value, key) {
            var regex = regexs[key];

            if (regex === undefined) {
              regex = regexs[key] = new RegExp(":" + key + "(?=/|$)");
            }
            url = url.replace(regex, value);
        });
        return url;
    };

    return {
        path: path,

        href: function (name, params) {
            return "#" + path(name, params);
        }
    };
});

Resolve regex forward lookahead issue


function MatchHighlighter() {
    'use strict';

    const mergeRanges = function(indexes) {
        return indexes.reduce(function(obj, index, pos) {
            const prevIndex = indexes[pos - 1] || 0;
            const currentIndex = indexes[pos];
            const nextIndex = indexes[pos + 1] || 0;

            if (currentIndex === nextIndex - 1 || currentIndex === prevIndex + 1 || indexes.length === 1) {
                obj.ranges[obj.rangeIndex].push(currentIndex);
            } else if (currentIndex > 0 && indexes.length > 1) {
                obj.rangeIndex = obj.ranges.push([currentIndex]);
            }
            return obj;
        }, {
            ranges:[[]],
            rangeIndex: 0
        }).ranges;
    };

    const wrapHighlight = function(letter) {
        return '<b>' + letter + '</b>';
    };

    this.highlight = function(input, result) {
        const matched = FuzzaldrinPlus.match(result, input);
        const substrings = mergeRanges(matched).map(function(range) {
            return range.map(function(index) {
                return result.charAt(index);
            }).join('');
        });

        return substrings.reduce(function(res, substring) {
            return res.replace(substring, wrapHighlight(substring));
        }, result);
    };

    return this;
}

function MatchHighlighter() {
    'use strict';

    const mergeRanges = function(indexes) {
        return indexes.reduce(function(obj, index, pos) {
            const prevIndex = indexes[pos - 1] || 0;
            const currentIndex = indexes[pos];
            const nextIndex = indexes[pos + 1] || 0;

            if (currentIndex === nextIndex - 1 || currentIndex === prevIndex + 1 || indexes.length === 1) {
                obj.ranges[obj.rangeIndex].push(currentIndex);
            } else if (currentIndex > 0 && indexes.length > 1) {
                obj.rangeIndex = obj.ranges.push([currentIndex]) - 1;
            }
            return obj;
        }, {
            ranges:[[]],
            rangeIndex: 0
        }).ranges;
    };

    const wrapHighlight = function(letter) {
        return '<b>' + letter + '</b>';
    };

    this.highlight = function(input, result) {
        const matched = FuzzaldrinPlus.match(result, input);
        const substrings = mergeRanges(matched).map(function(range) {
            return range.map(function(index) {
                return result.charAt(index);
            }).join('');
        });

        return substrings.reduce(function(res, substring) {
            return res.replace(substring, wrapHighlight(substring));
        }, result);
    };

    return this;
}

Fix off-by-one error in match highlighter


import React from 'react';
import VideoListItem from './video_list_item';

const VideoList = (props) => {
  const videoItems = props.videos.map((video) => {
    return <VideoListItem video = {video} />
  });

  return (
    <ul className="col-md-4 list-group">
      {videoItems}
    </ul>
  );
}

export default VideoList;

Add functionality to VideoList component


let chai = require('chai')
let chaiHttp = require('chai-http')
let server = require('../src/main.js')
import {expect} from 'chai'
chai.use(chaiHttp)

describe('server', () => {
  before( () => {
    server.listen();
  })

  after( () => {
    server.close();
  })

  describe('/', () => {
    it('should return 200', (done) => {
      chai.request(server)
        .get('/')
        .end((err, res) => {
          console.log(res);
          expect(res.status).to.equal(200);
          expect(res.text).to.equal('Node.js Server is running');
          server.close();
          done();
        })
    })
  })
})

let chai = require('chai')
let chaiHttp = require('chai-http')
let server = require('../src/main.js')
import {expect} from 'chai'
chai.use(chaiHttp)

describe('server', () => {
  before( () => {
    server.listen();
  })

  after( () => {
    server.close();
  })

  describe('/', () => {
    it('should return 200', (done) => {
      chai.request(server)
        .get('/')
        .end((err, res) => {
          expect(res.status).to.equal(200);
          expect(res.text).to.equal('Node.js Server is running');
          server.close();
          done();
        })
    })
  })
})

Remove logging from server test


import * as Backbone from 'backbone';
import Items from '../collections/items';
import SearchBoxView from '../views/searchBox-view';
import SearchResultsView from '../views/searchResults-view';
import AppView from '../views/app-view';
import DocumentSet from '../helpers/search';
import dispatcher from '../helpers/dispatcher';

class AppRouter extends Backbone.Router {

  get routes() {
    return {
      '': 'loadDefault',
      'search/(?*queryString)': 'showSearchResults'
    };
  }

  initialize() {
    this.listenTo(dispatcher, 'router:go', this.go);
  }

  go(route) {
    this.navigate(route, {trigger: true});
  }

  loadDefault() {
    new AppView();
  }

  showSearchResults(queryString) {
    let q = queryString.substring(2);

    DocumentSet.search(q).then(function(result) {
      let currentDocuments = new DocumentSet(result).documents;
      let docCollection = new Items(currentDocuments);
      new SearchResultsView({collection: docCollection}).render();
    });
  }

}

export default AppRouter;

import * as Backbone from 'backbone';
import Items from '../collections/items';
import SearchBoxView from '../views/searchBox-view';
import SearchResultsView from '../views/searchResults-view';
import AppView from '../views/app-view';
import Events from '../helpers/backbone-events';
import DocumentSet from '../helpers/search';

class AppRouter extends Backbone.Router {

  get routes() {
    return {
      '': 'loadDefault',
      'search/(?*queryString)': 'showSearchResults'
    };
  }

  initialize() {
    this.listenTo(Events, 'router:go', this.go);
  }

  go(route) {
    this.navigate(route, {trigger: true});
  }

  loadDefault() {
    new AppView();
  }

  showSearchResults(queryString) {
    let q = queryString.substring(2);

    DocumentSet.search(q).then(function(result) {
      let currentDocuments = new DocumentSet(result).documents;
      let docCollection = new Items(currentDocuments);
      new SearchResultsView({collection: docCollection}).render();
    });
  }

}

export default AppRouter;

Use Events instead of custom dispatcher (more)


// Imports
import UserRoles from '../../core/auth/constants/userRoles';
import WorkshopController from './workshop.controller';
import WorkshopHeaderController from './workshop-header.controller';
import { workshop } from './workshop.resolve';
import { carBrands } from '../home/home.resolve';

/**
 * @ngInject
 * @param RouterHelper
 */
export default function routing(RouterHelper) {
  const states = [{
    state: 'modules.workshop',
    config: {
      url: '/korjaamot/:id',
      title: 'Korjaamo',
      params: {
        id: null,
        selected: null,
      },
      data: {
        access: UserRoles.ROLE_ANON,
      },
      views: {
        'content@': {
          template: require('./partials/workshop.html'),
          controller: WorkshopController,
          controllerAs: 'vm',
          resolve: {
            _workshop: workshop,
            _carBrands: carBrands,
          },
        },
        'header@': {
          template: require('./partials/header.html'),
          controller: WorkshopHeaderController,
          controllerAs: 'vm',
          resolve: {
            _workshop: workshop,
          },
        },
      },
    },
  }];

  RouterHelper.configureStates(states);
}

// Imports
import UserRoles from '../../core/auth/constants/userRoles';
import WorkshopController from './workshop.controller';
import WorkshopHeaderController from './workshop-header.controller';
import { workshop } from './workshop.resolve';
import { carBrands } from '../home/home.resolve';

/**
 * @ngInject
 * @param RouterHelper
 * @param WorkshopSharedDataService
 */
export default function routing(RouterHelper, WorkshopSharedDataService) {
  const states = [{
    state: 'modules.workshop',
    config: {
      url: '/korjaamot/:id',
      title: 'Korjaamo',
      params: {
        id: null,
        selected: null,
      },
      data: {
        access: UserRoles.ROLE_ANON,
      },
      views: {
        'content@': {
          template: require('./partials/workshop.html'),
          controller: WorkshopController,
          controllerAs: 'vm',
          resolve: {
            _workshop: workshop,
            _carBrands: carBrands,
            _sharedData() {
              // Set action to information when routing to workshop details
              WorkshopSharedDataService.action = 'information';
            },
          },
        },
        'header@': {
          template: require('./partials/header.html'),
          controller: WorkshopHeaderController,
          controllerAs: 'vm',
          resolve: {
            _workshop: workshop,
          },
        },
      },
    },
  }];

  RouterHelper.configureStates(states);
}


/**
 * Created by farid on 8/16/2017.
 */
import React, {Component} from "react";
import Post from "./Post";
import PropTypes from 'prop-types';

class CategoryBody extends Component {

    render() {
        return (

                    {
                        this.props.posts.map((post, index) => (

                                <Post post={post}/>

                        ))
                    }

        )
    }
}

CategoryBody.propTypes = {
    posts: PropTypes.array.isRequired
};
export default CategoryBody;

/**
 * Created by farid on 8/16/2017.
 */
import React, {Component} from "react";
import Post from "./Post";
import PropTypes from 'prop-types';

class CategoryBody extends Component {

    render() {
        return (

                    {
                        this.props.posts.map((post, index) => (

                                <Post post={post} isEditEnabled={true} isDeleteEnabled={true}/>

                        ))
                    }

        )
    }
}

CategoryBody.propTypes = {
    posts: PropTypes.array.isRequired
};
export default CategoryBody;

Enable delete and update post on home page


// @flow

import * as React from 'react';
import { SafeAreaView } from 'react-navigation';

const forceInset = { top: 'always', bottom: 'never' };

type Props = {|
  style?: $PropertyType<React.ElementConfig<typeof SafeAreaView>, 'style'>,
  children?: React.Node,
|};
function InsetSafeAreaView(props: Props) {
  const { style, children } = props;
  return (
    <SafeAreaView forceInset={forceInset} style={style}>
      {children}
    </SafeAreaView>
  );
}

export default InsetSafeAreaView;

// @flow

import type { ViewStyle } from '../types/styles';

import * as React from 'react';
import { View } from 'react-native';
import { useSafeArea } from 'react-native-safe-area-context';

type Props = {|
  style?: ViewStyle,
  children?: React.Node,
|};
function InsetSafeAreaView(props: Props) {
  const insets = useSafeArea();
  const style = [
    { paddingTop: insets.top },
    props.style,
  ];
  return (
    <View style={style}>
      {props.children}
    </View>
  );
}

export default InsetSafeAreaView;

Update SafeAreaView for React Nav 5


var UserList = React.createClass({
    componentDidMount: function() {
        socket.on('join', this._join);
        socket.on('part', this._part);
    },
    render: function() {
        return (

        );
    },
    _join: function(username) {
        if (username != myUsername) {
            addOnlineUserToList(username);
        }
    },
    _part: function(username) {
        $('#online-list li span').filter(function() {
            return $(this).text() == username;
        }).parent().remove();
    }
});
React.render(<UserList />, document.getElementsByClassName('nicklist')[0]);

var UserList = React.createClass({
    componentDidMount: function() {
        socket.on('join', this._join);
        socket.on('part', this._part);
    },
    render: function() {
        return (

        );
    },
    _join: function(username) {
        if (username != myUsername) {
            addOnlineUserToList(username);
        }
    },
    _part: function(username) {
        $('#online-list li span').filter(function() {
            return $(this).text() == username;
        }).parent().remove();
    }
});

var User = React.createClass({
    render: function() {
        return (

        )
    }
});

React.render(<UserList />, document.getElementsByClassName('nicklist')[0]);

Create prototype react component for user in list


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

'use strict';

// [START trace_setup_explicit]
require('@google-cloud/trace-agent').start({
  projectId: 'your-project-id',
  keyFilename: '/path/to/key.json'
});
// [END trace_setup_explicity]

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

'use strict';

// [START trace_setup_explicit]
require('@google-cloud/trace-agent').start({
  projectId: 'your-project-id',
  keyFilename: '/path/to/key.json'
});
// [END trace_setup_explicit]

Fix type on region tag


module.exports = function(config){
  config.set({

    basePath : './',

    files : [
      'app/bower_components/angular/angular.js',
      'app/bower_components/angular-route/angular-route.js',
      'app/bower_components/angular-mocks/angular-mocks.js',
      'app/components/**/*.js',
      'app/view*/**/*.js'
    ],

    autoWatch : true,

    frameworks: ['jasmine'],

    browsers : ['Chrome'],

    plugins : [
            'karma-chrome-launcher',
            'karma-firefox-launcher',
            'karma-jasmine',
            'karma-junit-reporter'
            ],

    junitReporter : {
      outputFile: 'test_out/unit.xml',
      suite: 'unit'
    }

  });
};

module.exports = function(config){
  config.set({

    basePath : './',

    files : [
      'app/bower_components/angular/angular.js',
      'app/bower_components/angular-route/angular-route.js',
      'app/bower_components/angular-mocks/angular-mocks.js',
      'app/components/**/*.js',
      'app/view*/**/*.js'
    ],

    autoWatch : true,

    frameworks: ['jasmine'],

    browsers : ['Chrome', 'Firefox'],

    plugins : [
            'karma-chrome-launcher',
            'karma-firefox-launcher',
            'karma-jasmine',
            'karma-junit-reporter'
            ],

    junitReporter : {
      outputFile: 'test_out/unit.xml',
      suite: 'unit'
    }

  });
};

Change karma to run both Firefox and Chrome.


/* eslint-disable no-console */
import { CONFIG } from '../constants';
import { Socket } from 'phoenix';
import { startPlugin } from '..';
import listenAuth from './listenAuth';
import handleNotifications from './handleNotifications';
import handleWork from './handleWork';
import renderIcon from './renderIcon';

export const startChromePlugin = (auth, chrome, enhancer, socketConstructor = Socket) => {
  const reloader = () => window.location.reload(true);
  const plugin = startPlugin({ auth, enhancer, reloader, socketConstructor });
  const store = plugin.getStore();

  const getStore = () => store;

  listenAuth(store, chrome);
  handleNotifications(store, chrome);
  renderIcon(store, chrome);
  handleWork(store, chrome);

  if (CONFIG.env === 'dev') {
    // redux dev tools don't work with the plugin, so we have a dumb
    // replacement.
    store.subscribe(() => {
      const { worker, socket } = store.getState();
      console.log('Worker:', worker.toJS(), 'Socket:', socket.toJS());
    });
  }

  return { getStore };
};

/* eslint-disable no-console */
import { CONFIG } from '../constants';
import { Socket } from 'phoenix';
import { startPlugin } from '..';
import listenAuth from './listenAuth';
import handleNotifications from './handleNotifications';
import handleWork from './handleWork';
import renderIcon from './renderIcon';

export const startChromePlugin = (auth, chrome, enhancer, socketConstructor = Socket) => {
  const reloader = () => window.location.reload(true);
  const plugin = startPlugin({ auth, enhancer, reloader, socketConstructor });
  const store = plugin.getStore();

  const getStore = () => store;

  listenAuth(store, chrome);
  handleNotifications(store, chrome);
  renderIcon(store, chrome);
  handleWork(store, chrome);

  if (CONFIG.env === 'dev') {
    // redux dev tools don't work with the plugin, so we have a dumb
    // replacement.
    store.subscribe(() => {
      const { worker, socket, plugin: pluginState } = store.getState();
      console.log('Worker:', worker.toJS(),
                  'Socket:', socket.toJS(),
                  'Plugin:', pluginState.toJS());
    });
  }

  return { getStore };
};

Add plugin state to logging


var istanbul = require('browserify-istanbul');
var isparta = require('isparta');

module.exports = function (config) {
    'use strict';

    config.set({

        browsers: [
            'Chrome',
            'Firefox',
            'Safari',
            'Opera'
        ],
        reporters: ['progress', 'notify', 'coverage'],
        frameworks: ['browserify', 'mocha', 'chai-sinon', 'sinon'],

        // list of files / patterns to load in the browser
        files: ['./tests/spec/**/*.js'],

        preprocessors: {
            './tests/spec/**/*.js': ['browserify']
        },

        client: {
            mocha: {reporter: 'html'} // change Karma's debug.html to the mocha web reporter
        },

        browserify: {
            debug: true,
            transform: [
                //'babelify',
                istanbul({
                    instrumenter: isparta,
                    ignore: ['**/node_modules/**', '**/test/**'],
                })
            ],
            configure: function (bundle) {
                bundle.on('bundled', function (error) {
                    if (error != null)
                        console.error(error.message);
                });
            }
        },

        notifyReporter: {
            reportEachFailure: true,
            reportSuccess: true
        }
    });
};

var istanbul = require('browserify-istanbul');
var isparta = require('isparta');

module.exports = function (config) {
    'use strict';

    config.set({

        browsers: [
            //'PhantomJS',
            'Chrome',
            'Firefox',
            'Safari',
            'Opera'
        ],
        reporters: ['progress', 'notify', 'coverage'],
        frameworks: ['browserify', 'mocha', 'chai-sinon', 'sinon'],

        // list of files / patterns to load in the browser
        files: ['./tests/spec/**/*.js'],

        preprocessors: {
            './tests/spec/**/*.js': ['browserify']
        },

        client: {
            mocha: {reporter: 'html'} // change Karma's debug.html to the mocha web reporter
        },

        browserify: {
            debug: true,
            transform: [
                //'babelify',
                istanbul({
                    instrumenter: isparta,
                    ignore: ['**/node_modules/**', '**/test/**'],
                })
            ],
            configure: function (bundle) {
                bundle.on('bundled', function (error) {
                    if (error != null)
                        console.error(error.message);
                });
            }
        },

        notifyReporter: {
            reportEachFailure: true,
            reportSuccess: true
        },

        logLevel: 'LOG_INFO',
    });
};

Set log level. PhantomJS off.


/* @flow */
import React, { PureComponent } from 'react';
import type { ChildrenArray } from 'react';
import { View, Dimensions, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  popup: {
    marginRight: 20,
    marginLeft: 20,
    marginBottom: 2,
    bottom: 0,
    borderRadius: 5,
    shadowOpacity: 0.25,
    elevation: 3,
  },
});

type Props = {
  children: ChildrenArray<*>,
};

export default class Popup extends PureComponent<Props> {
  props: Props;

  static contextTypes = {
    styles: () => null,
  };

  render() {
    const { height } = Dimensions.get('window');

    return (
      <View style={[this.context.styles.backgroundColor, styles.popup]} maxHeight={height / 4}>
        {this.props.children}
      </View>
    );
  }
}

/* @flow */
import React, { PureComponent } from 'react';
import type { ChildrenArray } from 'react';
import { View, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  popup: {
    marginRight: 20,
    marginLeft: 20,
    marginBottom: 2,
    bottom: 0,
    borderRadius: 5,
    shadowOpacity: 0.25,
    elevation: 3,
  },
});

type Props = {
  children: ChildrenArray<*>,
};

export default class Popup extends PureComponent<Props> {
  props: Props;

  static contextTypes = {
    styles: () => null,
  };

  render() {
    return (
      <View style={[this.context.styles.backgroundColor, styles.popup]}>{this.props.children}</View>
    );
  }
}

Remove extraneous `maxHeight` prop on a `View`.


/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict-local
 * @format
 */

import * as React from 'react';
import {useContext} from 'react';
import GlobalPerformanceLogger from './GlobalPerformanceLogger';
import type {IPerformanceLogger} from './createPerformanceLogger';

/**
 * This is a React Context that provides a scoped instance of IPerformanceLogger.
 * We wrap every <AppContainer /> with a Provider for this context so the logger
 * should be available in every component.
 * See React docs about using Context: https://reactjs.org/docs/context.html
 */
const PerformanceLoggerContext: React.Context<IPerformanceLogger> = React.createContext(
  GlobalPerformanceLogger,
);
if (__DEV__) {
  PerformanceLoggerContext.displayName = 'PerformanceLoggerContext';
}

export function usePerformanceLogger(): IPerformanceLogger {
  return useContext(PerformanceLoggerContext);
}

export default PerformanceLoggerContext;

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict
 * @format
 */

import * as React from 'react';
import {useContext} from 'react';
import GlobalPerformanceLogger from './GlobalPerformanceLogger';
import type {IPerformanceLogger} from './createPerformanceLogger';

/**
 * This is a React Context that provides a scoped instance of IPerformanceLogger.
 * We wrap every <AppContainer /> with a Provider for this context so the logger
 * should be available in every component.
 * See React docs about using Context: https://reactjs.org/docs/context.html
 */
const PerformanceLoggerContext: React.Context<IPerformanceLogger> = React.createContext(
  GlobalPerformanceLogger,
);
if (__DEV__) {
  PerformanceLoggerContext.displayName = 'PerformanceLoggerContext';
}

export function usePerformanceLogger(): IPerformanceLogger {
  return useContext(PerformanceLoggerContext);
}

export default PerformanceLoggerContext;

Make some modules flow strict


// External
const mem = require('mem');

// Ours
const { merge } = require('../utils/structures');
const { getProjectConfig } = require('./project');

const PROJECT_TYPES_CONFIG = {
  preact: {
    plugins: [
      [
        require.resolve('@babel/plugin-transform-react-jsx'),
        {
          pragma: 'h'
        }
      ]
    ]
  },
  react: {
    presets: [require.resolve('@babel/preset-react')]
  }
};

module.exports.getBabelConfig = mem(({ isModernJS } = {}) => {
  const { babel: projectBabelConfig, pkg, type } = getProjectConfig();

  return merge(
    {
      presets: [
        [
          require.resolve('@babel/preset-env'),
          {
            targets: {
              browsers: isModernJS
                ? ['Chrome >= 76', 'Safari >= 12.1', 'iOS >= 12.3', 'Firefox >= 69', 'Edge >= 18']
                : pkg.browserslist || ['> 1% in au', '> 5%', 'Firefox ESR']
            },
            useBuiltIns: 'entry',
            corejs: 3,
            modules: process.env.NODE_ENV === 'test' ? 'commonjs' : false
          }
        ]
      ],
      plugins: [
        require.resolve('@babel/plugin-proposal-object-rest-spread'),
        require.resolve('@babel/plugin-syntax-dynamic-import'),
        require.resolve('@babel/plugin-proposal-class-properties')
      ]
    },
    PROJECT_TYPES_CONFIG[type],
    projectBabelConfig
  );
});

// External
const mem = require('mem');

// Ours
const { merge } = require('../utils/structures');
const { getProjectConfig } = require('./project');

const PROJECT_TYPES_CONFIG = {
  preact: {
    plugins: [
      [
        require.resolve('@babel/plugin-transform-react-jsx'),
        {
          pragma: 'h'
        }
      ]
    ]
  },
  react: {
    presets: [require.resolve('@babel/preset-react')]
  }
};

module.exports.getBabelConfig = mem(({ isModernJS } = {}) => {
  const { babel: projectBabelConfig, pkg, type } = getProjectConfig();

  return merge(
    {
      presets: [
        [
          require.resolve('@babel/preset-env'),
          {
            targets: {
              browsers: isModernJS
                ? ['Chrome >= 80', 'Safari >= 12.1', 'iOS >= 12.3', 'Firefox >= 72', 'Edge >= 18']
                : pkg.browserslist || ['> 1% in AU', 'Firefox ESR', 'IE 11']
            },
            useBuiltIns: 'entry',
            corejs: 3,
            modules: process.env.NODE_ENV === 'test' ? 'commonjs' : false
          }
        ]
      ],
      plugins: [
        require.resolve('@babel/plugin-proposal-object-rest-spread'),
        require.resolve('@babel/plugin-syntax-dynamic-import'),
        require.resolve('@babel/plugin-proposal-class-properties')
      ]
    },
    PROJECT_TYPES_CONFIG[type],
    projectBabelConfig
  );
});

Update target browsers. IE 11 needs to be manually specified now becuase it's dropped below 1% in Australia, but is still supported by ABC.


import walkTree from '../../../src/util/walk-tree';

describe('utils/walk-tree', function () {
  it('should walk parents before walking descendants', function () {
    var order = [];
    var one = document.createElement('one');

    one.innerHTML = '<two><three></three></two>';

    walkTree(one, function (elem) {
      order.push(elem.tagName);
    });

    expect(order[0]).to.equal('ONE');
    expect(order[1]).to.equal('TWO');
    expect(order[2]).to.equal('THREE');
  });
});

import walkTree from '../../../src/util/walk-tree';

describe('utils/walk-tree', function () {
  var one, order;

  beforeEach(function () {
    order = [];
    one = document.createElement('one');
    one.innerHTML = '<two><three></three></two>';
  });

  it('should walk parents before walking descendants', function () {
    walkTree(one, function (elem) {
      order.push(elem.tagName);
    });

    expect(order.length).to.equal(3);
    expect(order[0]).to.equal('ONE');
    expect(order[1]).to.equal('TWO');
    expect(order[2]).to.equal('THREE');
  });

  it('should accept a filter that filters out a node removing it and its descendants', function () {
    walkTree(one, function (elem) {
      order.push(elem.tagName);
    }, function (elem) {
      return elem.tagName !== 'TWO';
    });

    expect(order.length).to.equal(1);
    expect(order[0]).to.equal('ONE');
  });
});

Write test for walkTre() filter.


describe('Satellite game', function () {
  beforeEach(function () {

  });

  it('should exists', function () {
      expect(s).to.be.an('object');
      expect(s).to.have.property('config');
      expect(s).to.have.property('init');
  });

  it('should contains ship properties', function () {
    expect(s).to.have.deep.property('config.ship.hull');
    expect(s).to.have.deep.property('config.ship.shields');
    expect(s).to.have.deep.property('config.ship.maxSpeed');
  });

  it('should init the game', function () {
    var spy = sinon.spy();
    s.init('init', spy);
    spy.called.should.equal.true;

    expect(s).to.have.property('projector').and.to.be.an('object');
    expect(s).to.have.property('loader').and.to.be.an('object');
    expect(s).to.have.property('game').and.to.be.an('object');
  });
});

describe('Satellite game', function () {
  it('should exists', function () {
      expect(s).to.be.an('object');
      expect(s).to.have.property('config');
      expect(s).to.have.property('init');
  });

  it('should contains ship properties', function () {
    expect(s).to.have.deep.property('config.ship.hull');
    expect(s).to.have.deep.property('config.ship.shields');
    expect(s).to.have.deep.property('config.ship.maxSpeed');
  });

  it('should init the game', function () {
    var spy = sinon.spy();
    s.init('init', spy);
    spy.called.should.equal.true;

    expect(s).to.have.property('projector').and.to.be.an('object');
    expect(s).to.have.property('loader').and.to.be.an('object');
    expect(s).to.have.property('game').and.to.be.an('object');
  });
});

Remove unused before each call


$('.project-toggle-featured').on('click', function() {
    const projectId = $(this).data("project-id"),
          featured  = $(this).data("featured") === "True";
          method    =  featured ? "DELETE" : "POST";
    $.ajax({
          type: method,
          url: `/admin/featured/${projectId}`,
          dataType: 'json'
    }).done(() => {
        const newStatus = featured ? "False" : "True";
        $(this).data('featured', newStatus);
        $(this).html(newStatus);
    });
})

$('.project-toggle-featured').on('click', function() {
    const projectId = $(this).data("project-id"),
          featured  = $(this).val() === "Remove";
          method    =  featured ? "DELETE" : "POST";
    $.ajax({
          type: method,
          url: `/admin/featured/${projectId}`,
          dataType: 'json'
    }).done(() => {
        const newStatus = featured ? "Add" : "Remove";
        $(this).html(newStatus);
    });
})

Update featured projects button text


var webpack = require('webpack'),
    path = require('path'),
    CleanWebpackPlugin = require('clean-webpack-plugin');

var libraryName = 'webstompobs',
    dist = '/dist';

module.exports = {
  entry: __dirname + '/src/index.ts',
  context: path.resolve("./src"),
  output: {
    path: path.join(__dirname, dist),
    filename: libraryName + '.bundle.js',
    library: libraryName,
    libraryTarget: 'umd',
    umdNamedDefine: true
  },
  resolve: {
    extensions: ['.js', '.ts', '.jsx', '.tsx' ]
  },
  plugins: [
    new CleanWebpackPlugin(['dist'])
 ],
  module: {
    rules: [
      { test: /\.tsx?$/, loader: "awesome-typescript-loader" },
      { enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
    ]
  }
};

var webpack = require('webpack'),
    path = require('path'),
    CleanWebpackPlugin = require('clean-webpack-plugin');

var libraryName = 'webstompobs',
    dist = '/dist';

module.exports = {
  target: 'node',
  entry: __dirname + '/src/index.ts',
  context: path.resolve("./src"),
  output: {
    path: path.join(__dirname, dist),
    filename: libraryName + '.bundle.js',
    library: libraryName,
    libraryTarget: 'umd',
    umdNamedDefine: true
  },
  resolve: {
    extensions: ['.js', '.ts', '.jsx', '.tsx' ]
  },
  plugins: [
    new CleanWebpackPlugin(['dist'])
 ],
  module: {
    rules: [
      { test: /\.tsx?$/, loader: "awesome-typescript-loader" },
      { enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
    ]
  }
};

FIX : Version was not compatible with node


import React, { Component, PropTypes } from 'react';
import FormControl from 'react-bootstrap/lib/FormControl';

class Filter extends Component {

    constructor(props){
        super(props);
        this.state = {filterValue : this.props.query};
        this.inputChanged = this.inputChanged.bind(this);
    }

    componentWillUnmount(){
        if(this.timeout){
            window.clearTimeout(this.timeout);
            this.timeout = null;
        }
    }

    inputChanged(event){
        this.setState({filterValue: event.target.value});
        if (this.timeout){
            window.clearTimeout(this.timeout);
            this.timeout = null;
        }
        this.timeout = window.setTimeout(()=>{
            this.props.config.eventHandler(
                {
                    type:'filter-change',
                    id: this.props.config.id,
                    column: this.props.column,
                    query: this.state.filterValue
                }
            );
        }, 300);
    }

    render(){
        return(
            <FormControl id={'filter_for_'+this.props.column} type='search' key={this.props.column} value={this.state.filterValue} onChange={this.inputChanged} placeholder={"Filter..."} />
        );
    }
}

Filter.propTypes = {
    query: PropTypes.string,
    config: PropTypes.object,
    column: PropTypes.string.isRequired
};

export default Filter;

import React, { Component, PropTypes } from 'react';
import FormControl from 'react-bootstrap/lib/FormControl';

class Filter extends Component {

    constructor(props){
        super(props);
        this.state = {filterValue : this.props.query};
        this.inputChanged = this.inputChanged.bind(this);
    }

    componentWillUnmount(){
        if(this.timeout){
            window.clearTimeout(this.timeout);
            this.timeout = null;
        }
    }

    inputChanged(event){
        this.setState({filterValue: event.target.value});
        if (this.timeout){
            window.clearTimeout(this.timeout);
            this.timeout = null;
        }
        this.timeout = window.setTimeout(()=>{
            this.props.config.eventHandler(
                {
                    type:'filter-change',
                    id: this.props.config.id,
                    column: this.props.column,
                    query: this.state.filterValue
                }
            );
        }, 300);
    }

    render(){
        return(
            <FormControl id={'filter_for_'+this.props.column} type='search' key={this.props.column} value={this.state.filterValue} onChange={this.inputChanged} placeholder={'Filter...'} />
        );
    }
}

Filter.propTypes = {
    query: PropTypes.string,
    config: PropTypes.object,
    column: PropTypes.string.isRequired
};

export default Filter;

Use single quotes for strings


export default function userInterceptor($localStorage){
  function request(config){
    if(config.headers.Authorization){
      return config;
    }
    if($localStorage.token){
      config.headers.Authorization = 'bearer ' + $localStorage.token;
    }
    return config;
  }
  return {request};
}
userInterceptor.$inject = ['$localStorage'];

export default function userInterceptor(user){
  function request(config){
    if(config.headers.Authorization){
      return config;
    }
    if(user.token){
      config.headers.Authorization = 'bearer ' + user.token;
    }
    return config;
  }
  return {request};
}
userInterceptor.$inject = ['$localStorage'];

Use user service in interceptor


/**
 * Created by Tomasz Gabrysiak @ Infermedica on 08/02/2017.
 */

import html from '../../templates/helpers';

const template = (context) => {
  return new Promise((resolve) => {
    resolve(html`

Tell us how you feel.

        <form>

            We will try to recognize your symptoms using Natural Language Processing algorithms.
            <textarea placeholder="e.g. I got headache" class="form-control" id="input-feel" rows="4"></textarea>

        </form>

Identified observations:

 This screen uses our NLP engine to find symptoms in a written text. Evidences found in text will be marked as initial which is important to our engine. Please read more about initial evidences here. All of the identified symptoms will be added to your interview after clicking Next.

    `);
  });
};

export default template;

/**
 * Created by Tomasz Gabrysiak @ Infermedica on 08/02/2017.
 */

import html from '../../templates/helpers';

const template = (context) => {
  return new Promise((resolve) => {
    resolve(html`

Tell us how you feel.

        <form>

            We will try to recognize your symptoms using Natural Language Processing algorithms.
            <textarea placeholder="e.g. I got headache" class="form-control" id="input-feel" rows="4"></textarea>

        </form>

Identified observations:

 This screen uses our NLP engine to find symptoms in a written text. Evidence found in text will be marked as initial which is important to our engine. Please read more about initial evidence here. All of the identified symptoms will be added to your interview after clicking Next.

    `);
  });
};

export default template;

Fix typos in text about NLP


// Used to run webpack dev server to test the demo in local
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = (env, options) => ({
    mode: options.mode,
    devtool: 'source-map',
    entry: path.resolve(__dirname, 'demo/js/demo.js'),
    output: {
        path: path.resolve(__dirname, 'pages'),
        filename: '[name][chunkhash].js',
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: [/node_modules/],
                use: [
                    {
                        loader: 'babel-loader',
                    },
                ],
            },
            {
                test: /\.css$/,
                loaders: ['style-loader', 'css-loader'],
            },
        ],
    },

    plugins: [
        options.mode === 'development'
            ? new webpack.HotModuleReplacementPlugin()
            : () => {},
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, 'demo/index.html'),
        }),
    ],

    devServer: {
        contentBase: './demo',
    },
});

// Used to run webpack dev server to test the demo in local
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = (env, options) => ({
    mode: options.mode,
    devtool: 'source-map',
    entry: path.resolve(__dirname, 'demo/js/demo.js'),
    output: {
        path: path.resolve(__dirname, 'pages'),
        filename:
            options.mode === 'production'
                ? '[name][chunkhash].js'
                : '[name].js',
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: [/node_modules/],
                use: [
                    {
                        loader: 'babel-loader',
                    },
                ],
            },
            {
                test: /\.css$/,
                loaders: ['style-loader', 'css-loader'],
            },
        ],
    },

    plugins: [
        options.mode === 'development'
            ? new webpack.HotModuleReplacementPlugin()
            : () => {},
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, 'demo/index.html'),
        }),
    ],

    devServer: {
        contentBase: './demo',
    },
});

Fix chunkhash not allowed in development


const path = require('path');

module.exports = {
  entry: {
    'acrolinx-sidebar-integration': './src/acrolinx-sidebar-integration.ts',
    'tests': './test/index.ts'
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js']
  },
  output: {
    filename: '[name].js',
    publicPath: "/distrib/",
    path: path.resolve(__dirname, 'distrib')
  }
};

const path = require('path');

module.exports = {
  entry: {
    'acrolinx-sidebar-integration': './src/acrolinx-sidebar-integration.ts',
    'tests': './test/index.ts'
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        exclude: /node_modules/,
        options: {
          transpileOnly: true
        }
      }
    ]
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js']
  },
  output: {
    filename: '[name].js',
    publicPath: "/distrib/",
    path: path.resolve(__dirname, 'distrib')
  }
};

Use webpack just for transpileOnly of ts to js.


var webpack = require('webpack')

module.exports = {
  entry: {
    ui: './lib/ui/main.js',
    vendor: ['react', 'debug']
  },
  output: {
    path: 'dist',
    filename: '[name].js'
  },
  module: {
    loaders: [
      {test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel'},
      {test: /\.json$/, loader: 'json'}
    ]
  },
  resolve: {
    alias: {
      bacon: "baconjs"
    }
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'),
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
  ]
}

var webpack = require('webpack')

module.exports = {
  entry: {
    ui: './lib/ui/main.js',
    vendor: ['react', 'debug', 'd3', 'react-bootstrap']
  },
  output: {
    path: 'dist',
    filename: '[name].js'
  },
  module: {
    loaders: [
      {test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel'},
      {test: /\.json$/, loader: 'json'}
    ]
  },
  resolve: {
    alias: {
      bacon: "baconjs"
    }
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'),
    new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
  ]
}

Move stuff to vendor bundle


'use strict'

const path = require('path')
const nconf = require('nconf')

const defaults = {

  port: 5000,

  mongo: {
    url: 'mongodb://localhost/link-analyzer',
  },

  redis: {
    host: 'localhost',
    port: 6379,
  },

  kue: {
    prefix: 'q',
  },

}

nconf
  .file({ file: path.join(__dirname, '..', 'config.json') })
  .env({ separator: '_', lowerCase: true })
  .defaults({ store: defaults })

module.exports = nconf

'use strict'

const path = require('path')
const nconf = require('nconf')

const defaults = {

  port: 6000,

  mongo: {
    url: 'mongodb://localhost/link-analyzer',
  },

  redis: {
    host: 'localhost',
    port: 6379,
  },

  kue: {
    prefix: 'q',
  },

}

nconf
  .file({ file: path.join(__dirname, '..', 'config.json') })
  .env({ separator: '_', lowerCase: true })
  .defaults({ store: defaults })

module.exports = nconf

Change default port to 6000 as specified in geogw


function Translator(domain) {
  this.domain = domain;
  this.init();
}

Translator.prototype = {
  TYPE_ADD: 'add',
  MAPPED_FIELDS: {
    'id': '_key'
  },

  init: function() {
    this.table = this.getTableName(this.domain);
  },
  getTableName: function(domain) {
    return domain;
  },
  translate: function(batch) {

  },
  translateOne: function(batch) {
    switch (batch.type) {
      case this.TYPE_ADD:
        return this.addToLoad(batch);
      default:
        throw new Error('batch type "' + batch.type + '" is not acceptable');
    }
  },
  addToLoad: function(batch) {
    var line = {
          _key: batch.id
        };
    for (var field in batch.fields) {
      if (!batch.fields.hasOwnProperty(field))
        continue;
      line[field] = batch.fields[field];
    }
    var command = 'load --table ' + this.table + JSON.stringify([line]);
    return command;
  }
};

exports.Translator = Translator;

function Translator(domain) {
  this.domain = domain;
  this.init();
}

Translator.prototype = {
  TYPE_ADD: 'add',
  MAPPED_FIELDS: {
    'id': '_key'
  },

  init: function() {
    this.table = this.getTableName(this.domain);
  },
  getTableName: function(domain) {
    return domain;
  },
  translate: function(batch) {

  },
  translateOne: function(batch) {
    switch (batch.type) {
      case this.TYPE_ADD:
        return this.addToLoad(batch);
      default:
        throw new Error('batch type "' + batch.type + '" is not acceptable');
    }
  },
  addToLoad: function(batch) {
    var line = {
          _key: batch.id
        };
    for (var field in batch.fields) {
      if (!batch.fields.hasOwnProperty(field))
        continue;
      line[field] = batch.fields[field];
    }
    var command = 'load --table ' + this.table + ' ' + JSON.stringify([line]);
    return command;
  }
};

exports.Translator = Translator;

Add padding space between table name and loaded data


import {Events} from 'tabris';
import {mixin} from './helpers';

const store = {};

class Settings {

  get serverUrl() {
    return store.serverUrl;
  }

  set serverUrl(url) {
    store.serverUrl = url;
    localStorage.setItem('serverUrl', url);
    this.trigger('change:serverUrl');
  }

  load() {
    store.serverUrl = localStorage.getItem('serverUrl') || 'http://192.168.1.';
  }

}

mixin(Settings, Events);

export default new Settings();

import {Events} from 'tabris';
import {mixin} from './helpers';

const store = {};

class Settings {

  get serverUrl() {
    return store.serverUrl;
  }

  set serverUrl(url) {
    store.serverUrl = url;
    localStorage.setItem('serverUrl', url);
    this.trigger('change:serverUrl');
  }

  load() {
    store.serverUrl = localStorage.getItem('serverUrl') || 'http://192.168.1.1';
  }

}

mixin(Settings, Events);

export default new Settings();

Use complete IP address as default


var inherits = require('inherits');

var self = module.exports = {
  FieldValidationError: FieldValidationError,
  ModelValidationError: ModelValidationError,

  messages: {
    "en-us": {
      "generic": "Failed to validate field",
      "required": "The field is required",
      "wrong_type": "Field value is not of type {type}"
    }
  },

  getMessage: function(type, params, lang) {
    lang = lang || "en-us";
    return self.messages[lang][type].replace(/\${([\w:]+)}/g, function(match, varname) {
      return params[varname];
    });
  }
};

function FieldValidationError(type, params) {
  this.type = type || 'generic';
  this.params = params;

  this.message = self.getMessage(type, params);
  this.stack = new Error(this.message).stack;
}
inherits(FieldValidationError, Error);

function ModelValidationError(errors) {
  this.errors = errors;
  this.message = "Error validating the model \n"+JSON.stringify(errors);
  this.stack = new Error(this.message).stack;
}
inherits(ModelValidationError, Error);

var inherits = require('inherits');

var self = module.exports = {
  FieldValidationError: FieldValidationError,
  ModelValidationError: ModelValidationError,

  messages: {
    "en-us": {
      "generic": "Failed to validate field",
      "required": "The field is required",
      "wrong_type": "Field value is not of type {type}"
    }
  },

  getMessage: function(type, params, lang) {
    lang = lang || "en-us";
    return self.messages[lang][type].replace(/\${([\w:]+)}/g, function(match, varname) {
      return params[varname];
    });
  }
};

function FieldValidationError(type, params) {
  this.type = type || 'generic';
  this.params = params;

  this.message = self.getMessage(type, params);
  this.stack = new Error(this.message).stack;
}
inherits(FieldValidationError, Error);

function ModelValidationError(errors) {
  this.errors = errors;
  var message = "Error validating the model \n";
  Object.keys(errors).forEach(function(field) {
    message += field + ": " + errors[field].message + "\n";
  });
  this.message = message;
  this.stack = new Error(this.message).stack;
}
inherits(ModelValidationError, Error);

Improve formatting of ModelValidationError toString()


$(document).ready(function(){
  $.jqplot('gets-sets', [$("#gets-sets").data("gets"), $("#gets-sets").data("sets")], {
    title: 'Gets & Sets',
    grid: {
      drawBorder: false,
      shadow: false,
      background: '#fefefe'
    },
    axesDefaults: {
      labelRenderer: $.jqplot.CanvasAxisLabelRenderer
    },
    seriesDefaults: {
      rendererOptions: {
        smooth: true
      }
    }
  });
  $.jqplot('hits-misses', [$("#hits-misses").data("hits"), $("#hits-misses").data("misses")], {
    title: 'Hits & Misses',
    grid: {
      drawBorder: false,
      shadow: false,
      background: '#fefefe'
    },
    axesDefaults: {
      labelRenderer: $.jqplot.CanvasAxisLabelRenderer
    },
    seriesDefaults: {
      rendererOptions: {
        smooth: true
      }
    }
  });
});

$(document).ready(function(){
  $.jqplot('gets-sets', [$("#gets-sets").data("gets"), $("#gets-sets").data("sets")], {
    title: 'Gets & Sets',
    grid: {
      drawBorder: false,
      shadow: false,
      background: '#fefefe'
    },
    axesDefaults: {
      labelRenderer: $.jqplot.CanvasAxisLabelRenderer
    },
    seriesDefaults: {
      rendererOptions: {
        smooth: true
      }
    },
    series: [
      {label: 'Gets'},
      {label: 'Sets'}
   ],
   legend: {
    show: true
   },
   seriesColors: ["rgb(36, 173, 227)", "rgb(227, 36, 132)"]
  });
  $.jqplot('hits-misses', [$("#hits-misses").data("hits"), $("#hits-misses").data("misses")], {
    title: 'Hits & Misses',
    grid: {
      drawBorder: false,
      shadow: false,
      background: '#fefefe'
    },
    axesDefaults: {
      labelRenderer: $.jqplot.CanvasAxisLabelRenderer
    },
    seriesDefaults: {
      rendererOptions: {
        smooth: true
      }
     },
     series: [
       {label: 'Hits'},
       {label: 'Misses'}
    ],
    legend: {
     show: true
    },
    seriesColors: ["rgb(227, 36, 132)", "rgb(227, 193, 36)"]
  });
});

Set legends and colors of series


/* eslint-disable max-len */
import dayjs from "dayjs";

export const GDQ_API_ENDPOINT = "https://api.gdqstat.us";

export const OFFLINE_MODE = true;

const LIVE_STORAGE_ENDPOINT = "https://storage.api.gdqstat.us";

// Note: Keep this up-to-date with the most recent event
export const EVENT_YEAR = 2019;
export const EVENT_SHORT_NAME = "agdq";
export const EVENT_START_DATE = dayjs("01-05-20");

const OFFLINE_STORAGE_ENDPOINT = `/data/${EVENT_YEAR}/${EVENT_SHORT_NAME}_final`;

export const GDQ_STORAGE_ENDPOINT = OFFLINE_MODE
  ? OFFLINE_STORAGE_ENDPOINT
  : LIVE_STORAGE_ENDPOINT;

export const DONATION_TRACKER_URL = `https://gamesdonequick.com/tracker/index/${EVENT_SHORT_NAME}${EVENT_YEAR}`;

export const SECONDARY_COLOR = "#F21847";
export const PRIMARY_COLOR = "#00AEEF";
export const PANEL_BACKGROUND_COLOR = "#EEEEEE";
export const LIGHT_FILL_COLOR = "#DDDDDD";
export const DARK_FILL_COLOR = "#333333";

/* eslint-disable max-len */
import dayjs from "dayjs";

export const GDQ_API_ENDPOINT = "https://api.gdqstat.us";

export const OFFLINE_MODE = true;

const LIVE_STORAGE_ENDPOINT = "https://storage.api.gdqstat.us";

// Note: Keep this up-to-date with the most recent event
export const EVENT_YEAR = 2020;
export const EVENT_SHORT_NAME = "agdq";
export const EVENT_START_DATE = dayjs("01-05-20");

const OFFLINE_STORAGE_ENDPOINT = `/data/${EVENT_YEAR}/${EVENT_SHORT_NAME}_final`;

export const GDQ_STORAGE_ENDPOINT = OFFLINE_MODE
  ? OFFLINE_STORAGE_ENDPOINT
  : LIVE_STORAGE_ENDPOINT;

export const DONATION_TRACKER_URL = `https://gamesdonequick.com/tracker/index/${EVENT_SHORT_NAME}${EVENT_YEAR}`;

export const SECONDARY_COLOR = "#F21847";
export const PRIMARY_COLOR = "#00AEEF";
export const PANEL_BACKGROUND_COLOR = "#EEEEEE";
export const LIGHT_FILL_COLOR = "#DDDDDD";
export const DARK_FILL_COLOR = "#333333";

Fix offline endpoint to point to 2020 instead of 2019


// import { test, getRenderedComponent } from '../spec_helper'
// import { default as subject } from '../../src/components/buttons/Button'

// test('#render', (assert) => {
//   const button = getRenderedComponent(subject, {className: 'MyButton'}, 'Yo')
//   assert.equal(button.type, 'button')
//   assert.equal(button.props.className, 'MyButton Button')
//   assert.equal(button.props.classListName, 'Button')
//   assert.equal(button.props.children, 'Yo')
//   assert.end()
// })

import { expect, getRenderedComponent } from '../spec_helper'
import { default as subject } from '../../src/components/buttons/Button'

describe('Button#render', () => {
  it('renders correctly', () => {
    const button = getRenderedComponent(subject, {className: 'MyButton'}, 'Yo')
    expect(button.type).to.equal('button')
    expect(button.props.className).to.equal('MyButton Button')
    expect(button.props.classListName).to.equal('Button')
    expect(button.props.children).to.equal('Yo')
  })
})

Add back in the button tests


import { combineReducers } from 'redux'
import { RECEIVE_STOP_DATA } from '../actions'

const cardsReducer = () => {
  return {cards: [{name: 'Fruängen', stopId: '9260', updating: false, error: false},{name: 'Slussen', stopId: '9192', updating: false, error: false}]}
}

const stopsReducer = (state, action) => {
  switch (action.type) {
  case RECEIVE_STOP_DATA:
    var s = Object.assign({}, state)
    s.stops[action.stop] = action.data.departures
    return s

  default:
    return state == undefined ? {stops: {}} : state
  }
}

const departuresApp = combineReducers({
  cardsReducer,
  stopsReducer
})

export default departuresApp

import { combineReducers } from 'redux'
import { RECEIVE_STOP_DATA } from '../actions'

const cardsReducer = () => {
  return {cards: [
    {
      name: 'Fruängen',
      stopId: '9260',
      updating: false,
      error: false,
      lines: [{
        line: '14',
        direction: 1
      }]
    },
    {
      name: 'Slussen',
      stopId: '9192',
      updating: false,
      error: false,
      lines: [{
        line: '14',
        direction: 2
      }]
    }]}
}

/*
 * Filter new stop information and add them to the new state
 */
const stopsReducer = (state, action) => {
  switch (action.type) {
  case RECEIVE_STOP_DATA:
    var newState = Object.assign({}, state)
    newState.stops[action.stop] = action.data.departures
    return newState

  default:
    return state == undefined ? {stops: {}} : state
  }
}

const departuresApp = combineReducers({
  cardsReducer,
  stopsReducer
})

export default departuresApp

Add direction information to cards


'use strict';

var winston = require('winston');
var expressWinston = require('express-winston');

var requestLogger = expressWinston.logger({
  transports: [
    new winston.transports.Console({
      json: true,
      colorize: true
    })
  ],
  meta: true,
  msg: 'HTTP {{req.method}} {{req.url}}',
  expressFormat: true,
  colorStatus: true
});

var errorLogger = expressWinston.errorLogger({
  transports: [
    new winston.transports.Console({
      json: true,
      colorize: true
    })
  ]
});

var logger = new (winston.Logger)({
  transports: [
    new (winston.transports.Console)({
      timestamp: function() {
        return Date.now();
      },
      formatter: function(options) {
        // Return string will be passed to logger.
        return options.timestamp() +' '+ options.level.toUpperCase() +' '+ (undefined !== options.message ? options.message : '') +
          (options.meta && Object.keys(options.meta).length ? '\n\t'+ JSON.stringify(options.meta) : '' );
      }
    })
  ]
});

module.exports = {
  request: requestLogger,
  error: errorLogger,
  logger: logger
};

'use strict';

var winston = require('winston');
var expressWinston = require('express-winston');

var requestLogger = expressWinston.logger({
  transports: [
    new winston.transports.Console({
      json: true,
      colorize: true
    })
  ],
  meta: true,
  msg: 'HTTP {{req.method}} {{req.url}}',
  expressFormat: true,
  colorStatus: true
});

var errorLogger = expressWinston.errorLogger({
  transports: [
    new winston.transports.Console({
      json: true,
      colorize: true
    })
  ]
});

var logger = new (winston.Logger)({
  transports: [
    new (winston.transports.Console)({
      timestamp: function() {
        return Date.now();
      },
      formatter: function(options) {
        // Return string will be passed to logger.
        return options.timestamp() +' '+ options.level.toLowerCase() + ': '+ (undefined !== options.message ? options.message : '') +
          (options.meta && Object.keys(options.meta).length ? '\n\t'+ JSON.stringify(options.meta) : '' );
      }
    })
  ]
});

module.exports = {
  request: requestLogger,
  error: errorLogger,
  logger: logger
};

Change log level to lowercase


import Obstacle from './Obstacle'

export default class extends Obstacle {
  constructor (game, player, x, y, frame, bulletFrame) {
    super(game, player, x, y, frame)

    this.weapon = this.game.plugins.add(Phaser.Weapon)
    this.weapon.trackSprite(this)
    this.weapon.createBullets(50, 'chars_small', bulletFrame)
    this.weapon.bulletSpeed = 600
    this.weapon.fireRate = 200

    this.target = null
  }

  update () {
    super.update()

    this.game.physics.arcade.overlap(this.player, this.weapon.bullets, this.onCollision, null, this)

    if (!this.inCamera) {
      return
    }

    if (this.target != null) {
      this.weapon.fireAtSprite(this.target)
    } else if (this.weapon.fire()) {
      this.weapon.fireAngle += 30
    }
  }

  onCollision () {
    super.onCollision()
    const saved = this.target
    this.target = null
    setTimeout(() => this.target = saved, 1000)
  }
}

import Obstacle from './Obstacle'

export default class extends Obstacle {
  constructor (game, player, x, y, frame, bulletFrame) {
    super(game, player, x, y, frame)

    this.weapon = this.game.plugins.add(Phaser.Weapon)
    this.weapon.trackSprite(this)
    this.weapon.createBullets(50, 'chars_small', bulletFrame)
    this.weapon.bulletSpeed = 600
    this.weapon.fireRate = 200

    this.target = null
  }

  update () {
    super.update()

    this.game.physics.arcade.overlap(this.player, this.weapon.bullets, this.onCollision, null, this)

    if (!this.inCamera) {
      return
    }

    if (this.target != null) {
      this.weapon.fireAtSprite(this.target)
    } else if (this.weapon.fire()) {
      this.weapon.fireAngle += 30
    }
  }

  onCollision () {
    super.onCollision()
    if (this.target != null) {
      const saved = this.target
      this.target = null
      setTimeout(() => this.target = saved, 1000)
    }
  }
}

Fix reaggroing when player gets hit while deaggrod.


var fs = require('fs');
var path = require('path');
var exec = require('child_process').exec;
var semver = require('semver');
var sh = require('execSync');
var packageJSONPath = path.join(process.cwd(), 'package.json');
var packageJSON = require(packageJSONPath);

module.exports = function(newVersion, commitMessage){
  var newSemver = packageJSON.version = getNewSemver(newVersion);
  fs.writeFileSync(packageJSONPath, JSON.stringify(packageJSON, null, 2));
  commitMessage = (commitMessage || 'Release v%s').replace(/%s/g, newSemver);
  runCommand('git commit -am "' + commitMessage + '"');
  runCommand('git tag v' + newSemver);
  runCommand('git push origin --all');
  runCommand('git push origin --tags');
  if (!packageJSON.private) runCommand('npm publish');
};

function getNewSemver(newVersion) {
  var newSemver = semver.valid(newVersion);
  if (!newSemver) newSemver = semver.inc(packageJSON.version, newVersion);
  if (!newSemver) throw new Error('Invalid new version');
  return newSemver;
}

function getCommitMessage(commitMessage, newSemver) {
  commitMessage = commitMessage || 'Release v%s';
  return commitMessage.replace(/%s/g, newSemver);
}

function runCommand(cmd) {
  if (!sh.run(cmd)) throw new Error('[' + command + '] failed');
}

var fs = require('fs');
var path = require('path');
var exec = require('child_process').exec;
var semver = require('semver');
var sh = require('execSync');
var packageJSONPath = path.join(process.cwd(), 'package.json');
var packageJSON = require(packageJSONPath);

module.exports = function(newVersion, commitMessage){
  var newSemver = packageJSON.version = getNewSemver(newVersion);
  fs.writeFileSync(packageJSONPath, JSON.stringify(packageJSON, null, 2));
  commitMessage = (commitMessage || 'Release v%s').replace(/%s/g, newSemver);
  runCommand('git commit -am "' + commitMessage + '"');
  runCommand('git tag v' + newSemver);
  runCommand('git push origin --all');
  runCommand('git push origin --tags');
  if (!packageJSON.private) runCommand('npm publish');
};

function getNewSemver(newVersion) {
  var newSemver = semver.valid(newVersion);
  if (!newSemver) newSemver = semver.inc(packageJSON.version, newVersion);
  if (!newSemver) throw new Error('Invalid new version');
  return newSemver;
}

function getCommitMessage(commitMessage, newSemver) {
  commitMessage = commitMessage || 'Release v%s';
  return commitMessage.replace(/%s/g, newSemver);
}

function runCommand(cmd) {
  if (sh.run(cmd)) throw new Error('[' + command + '] failed');
}

Throw errors on non-zero cmd exit codes


const genDefaultConfig = require('@storybook/vue/dist/server/config/defaults/webpack.config.js')
const merge = require('webpack-merge')

module.exports = (baseConfig, env) => {
  const storybookConfig = genDefaultConfig(baseConfig, env)
  const quasarConfig = require('../build/webpack.dev.conf.js')

  /* when building with storybook we do not want to extract css as we normally do in production */
  process.env.DISABLE_EXTRACT_CSS = true
  const quasarBasePlugins = require('../build/webpack.base.conf.js').plugins

  // use Quasar config as default
  let mergedConfig = merge(quasarConfig, storybookConfig)

  // set Storybook entrypoint
  mergedConfig.entry = storybookConfig.entry

  // allow relative module resolution as used in Storybook
  mergedConfig.resolve.modules = storybookConfig.resolve.modules

  // only use Quasars loaders
  mergedConfig.module.rules = quasarConfig.module.rules

  // enable Storybook http server
  mergedConfig.plugins = storybookConfig.plugins
  // get Quasar's DefinePlugin and PostCSS settings
  mergedConfig.plugins.unshift(quasarBasePlugins[0], quasarBasePlugins[1])

  return mergedConfig
}

const genDefaultConfig = require('@storybook/vue/dist/server/config/defaults/webpack.config.js')
const merge = require('webpack-merge')

module.exports = (baseConfig, env) => {

  /* when building with storybook we do not want to extract css as we normally do in production */
  process.env.DISABLE_EXTRACT_CSS = true

  const storybookConfig = genDefaultConfig(baseConfig, env)
  const quasarConfig = require('../build/webpack.dev.conf.js')
  const quasarBasePlugins = require('../build/webpack.base.conf.js').plugins

  // use Quasar config as default
  let mergedConfig = merge(quasarConfig, storybookConfig)

  // set Storybook entrypoint
  mergedConfig.entry = storybookConfig.entry

  // allow relative module resolution as used in Storybook
  mergedConfig.resolve.modules = storybookConfig.resolve.modules

  // only use Quasars loaders
  mergedConfig.module.rules = quasarConfig.module.rules

  // enable Storybook http server
  mergedConfig.plugins = storybookConfig.plugins
  // get Quasar's DefinePlugin and PostCSS settings
  mergedConfig.plugins.unshift(quasarBasePlugins[0], quasarBasePlugins[1])

  return mergedConfig
}

Set DISABLE_EXTRACT_CSS in correct place


import { expect } from 'chai';
import { mount } from 'enzyme';
import React from 'react';
import Post from '../../src/js/components/Post';

describe('<Post/>', () => {
  const post = {
    id: 0,
    title: 'Test Post',
    content: 'empty',
    description: 'empty',
    author: 'bot',
    slug: 'test-post',
    tags: ['test', 'react'],
  };

  it('renders post title', () => {
    const result = mount(<Post post={post} />);
    expect(result.find('.post__title').text()).to.eq('Test Post');
  });

  it('renders post metadata', () => {
    const result = mount(<Post post={post} />);
    expect(result.find('.post__meta').text()).to.eq('  a few seconds ago');
  });

  it('renders post image', () => {
    const postWithImage = { ...post, img: 'image.png' };
    const result = mount(<Post post={postWithImage} />);
    expect(result.find('.post__image').length).to.eq(1);
  });

  it('renders no image if missing', () => {
    const result = mount(<Post post={post} />);
    expect(result.find('.post__image').length).to.eq(0);
  });
});

import { expect } from 'chai';
import { mount, shallow } from 'enzyme';
import React from 'react';
import Post from '../../src/js/components/Post';

describe('<Post/>', () => {
  const post = {
    id: 0,
    title: 'Test Post',
    content: 'empty',
    description: 'empty',
    author: 'bot',
    slug: 'test-post',
    tags: ['test', 'react'],
  };

  it('renders post title', () => {
    const result = shallow(<Post post={post} />);
    expect(result.find('.post__title').text()).to.eq('Test Post');
  });

  it('renders post metadata', () => {
    const result = mount(<Post post={post} />);
    expect(result.find('.post__meta').text()).to.eq('  a few seconds ago');
  });

  it('renders post image', () => {
    const postWithImage = { ...post, img: 'image.png' };
    const result = shallow(<Post post={postWithImage} />);
    expect(result.find('.post__image').length).to.eq(1);
  });

  it('renders no image if missing', () => {
    const result = mount(<Post post={post} />);
    expect(result.find('.post__image').length).to.eq(0);
  });
});

Replace `mount` calls with `shallow` in <Post> tests


#!/usr/bin/env node
'use strict';

/** Load modules. */
var fs = require('fs'),
    path = require('path');

/** Resolve program params. */
var args = process.argv.slice(process.argv[0] === process.execPath ? 2 : 0),
    filePath = path.resolve(args[1]);

var pattern = (function() {
  var result = args[0],
      delimiter = result.charAt(0),
      lastIndex = result.lastIndexOf(delimiter);

  return RegExp(result.slice(1, lastIndex), result.slice(lastIndex + 1));
}());

/** Used to match lines of code. */
var reLine = /.*/gm;

/*----------------------------------------------------------------------------*/

fs.writeFileSync(filePath, fs.readFileSync(filePath, 'utf-8').replace(pattern, function(match) {
  return match.replace(reLine, '');
}), 'utf-8');

#!/usr/bin/env node
'use strict';

/** Load modules. */
var fs = require('fs'),
    path = require('path');

/** Resolve program params. */
var args = process.argv.slice(process.argv[0] === process.execPath ? 2 : 0),
    filePath = path.resolve(args[1]);

var pattern = (function() {
  var result = args[0],
      delimiter = result.charAt(0),
      lastIndex = result.lastIndexOf(delimiter);

  return RegExp(result.slice(1, lastIndex), result.slice(lastIndex + 1));
}());

/** Used to match lines of code. */
var reLine = /.*/gm;

/*----------------------------------------------------------------------------*/

fs.writeFileSync(filePath, fs.readFileSync(filePath, 'utf-8').replace(pattern, function(match) {
  return match.replace(reLine, '');
}));

Remove 'utf-8' option because it's the default.


QUnit.test( "hello test", function( assert ) {
  assert.ok( 1 == "1", "Passed!" );
});

QUnit.test( "getOptions default", function( assert ) {

    var options = $.fn.inputFileText.getOptions();

    assert.equal(options.text,
        'Choose File',
        'Should return default text option when no text option is provided.');

    assert.equal(options.remove,
        false,
        'Should return default remove option when no text option is provided.');
});

Test that getOptions returns default options when none are provided


'use strict';

/**
 * @ngdoc directive
 * @name grafterizerApp.directive:clojurePipeline
 * @description
 * # clojurePipeline
 */
angular.module('grafterizerApp')
  .directive('clojurePipeline', function (generateClojure) {
    return {
      template: '

',
      restrict: 'E',
      scope: {
        transformation: '=',
      },
      link: {
      	pre: function(scope) {
		    scope.editorOptions = {
		        lineWrapping : true,
		        // lineNumbers: true,
		        mode: 'clojure',
		        readOnly: true
		    };
      	},
      	post: function(scope, element, attrs) {
      		scope.$watch('transformation', function(){
      			if (!scope.transformation) {
              return;
            }
      			// console.log(scope.transformation)
      			scope.clojure = generateClojure.fromTransformation(scope.transformation);
      		}, true);
      	}
      }
    };
  });

'use strict';

/**
 * @ngdoc directive
 * @name grafterizerApp.directive:clojurePipeline
 * @description
 * # clojurePipeline
 */
angular.module('grafterizerApp')
  .directive('clojurePipeline', function (generateClojure) {
    return {
      template: '

',
      restrict: 'E',
      scope: {
        transformation: '=',
      },
      link: {
      	pre: function(scope) {
		    scope.editorOptions = {
		        lineWrapping : true,
		        // lineNumbers: true,
		        mode: 'clojure',
		        readOnly: true
		    };
      	},
      	post: function(scope, element, attrs) {
      		scope.$watch('transformation', function(){
      			if (!scope.transformation) {
              return;
            }
      			// console.log(scope.transformation)
      			scope.clojure = generateClojure.fromTransformation(scope.transformation);
      		}, true);

          // TODO workaround random bug
          scope.$watch("$parent.selectedTabIndex", function(){
            if (scope['$parent'].selectedTabIndex) {
              window.setTimeout(function(){
              try {
                element.children().children()[0].CodeMirror.refresh();
              }catch(e){}
            }, 1);
            }
          });
      	}
      }
    };
  });

Refresh the CodeMirror view if it's inside a md-tabs


/**
 * Copyright © 2017 Highpine. All rights reserved.
 *
 * @author    Max Gopey <gopeyx@gmail.com>
 * @copyright 2017 Highpine
 * @license   https://opensource.org/licenses/MIT  MIT License
 */

class ApiError {
    constructor(message) {
        this.message = message;
        this.stack = Error().stack;
    }
    static withStatusCode (statusCode, message) {
        let apiError = new ApiError(message);
        apiError.status = statusCode;
        return apiError;
    };
}

module.exports = ApiError;

/**
 * Copyright © 2017 Highpine. All rights reserved.
 *
 * @author    Max Gopey <gopeyx@gmail.com>
 * @copyright 2017 Highpine
 * @license   https://opensource.org/licenses/MIT  MIT License
 */

class ApiError extends Error {
    constructor(message, id) {
        super(message, id);
        this.message = message;
        this.stack = Error().stack;
    }
    static withStatusCode (statusCode, message) {
        let apiError = new ApiError(message);
        apiError.status = statusCode;
        return apiError;
    };
}

module.exports = ApiError;

Add inheritance from Error for ApiError


// import axios from 'axios';

export const FETCH_SEARCH_RESULTS = 'fetch_search_results';

// const ROOT_URL = 'http://herokuapp.com/';

export function fetchSearchResults(term, location) {
  // const request = axios.get(`${ROOT_URL}/${term}/${location}`);

  const testJSON = {
    data: [
            { name: 'Michael Mina', url: 'yelp.com/michaelminna' },
            { name: 'Sushi', url: 'yelp.com/sushi' },
            { name: 'Thai', url: 'yelp.com/thai' },
            { name: 'Chinese', url: 'yelp.com/chinese' }
          ]
  };

  return {
    type: FETCH_SEARCH_RESULTS,
    payload: testJSON
  }
}

import axios from 'axios';

export const FETCH_SEARCH_RESULTS = 'fetch_search_results';

const ROOT_URL = 'https://earlybirdsearch.herokuapp.com/?';

export function fetchSearchResults(term, location) {
  const request = axios.get(`${ROOT_URL}business=${term}&location=${location}`);

  console.log(request);

  // const testJSON = {
  //   data: [
  //           { name: 'Michael Mina', url: 'yelp.com/michaelminna' },
  //           { name: 'Sushi', url: 'yelp.com/sushi' },
  //           { name: 'Thai', url: 'yelp.com/thai' },
  //           { name: 'Chinese', url: 'yelp.com/chinese' }
  //         ]
  // };

  return {
    type: FETCH_SEARCH_RESULTS,
    payload: request
  }
}

Add heroku url to axios request


var _ = require('underscore');

module.exports = function(dbConfig) {

	var db = require('./poemlab_database')(dbConfig);

	return {

		create: function(user_data, callback) {
			var params = _.values(_.pick(user_data, ["name", "email", "password"]));
			db.query("insert into poets (name, email, password) values ($1, $2, $3) returning id", params,
				function(err, result) {
					if (err) { return callback(err); }
					var user = { id: result.rows[0].id };
					callback(null, user);
				}
			);
		},

		read: function(user_id, callback) {
			db.query("select * from poets where id = $1", [user_id], function(err, result) {
				if (err) { return callback(err); }
				callback(null, result.rows[0]);
			});
		},

		destroy: function(user_id, callback) {
			db.query("delete from poets where id = $1", [user_id], function(err, result) {
				callback(err);
			});
		},

		all: function(callback) {
			db.query("select * from poets", [], function(err, result) {
				if (err) { return callback(err); }
				callback(null, result.rows);
			});
		}

	};

};

var _ = require('underscore');

module.exports = function(dbConfig) {

	var db = require('./poemlab_database')(dbConfig);

	return {

		create: function(user_data, callback) {
			var params = _.values(_.pick(user_data, ["name", "email", "password"]));
			db.query("insert into poets (name, email, password) values ($1, $2, $3) " +
					"returning id, name, email", params,
				function(err, result) {
					if (err) { return callback(err); }
					callback(null, result.rows[0]);
				}
			);
		},

		read: function(user_id, callback) {
			db.query("select * from poets where id = $1", [user_id], function(err, result) {
				if (err) { return callback(err); }
				callback(null, result.rows[0]);
			});
		},

		destroy: function(user_id, callback) {
			db.query("delete from poets where id = $1", [user_id], function(err, result) {
				callback(err);
			});
		},

		all: function(callback) {
			db.query("select * from poets", [], function(err, result) {
				if (err) { return callback(err); }
				callback(null, result.rows);
			});
		}

	};

};

Return all poet attributes except password from create method


/*! CSS rel=preload polyfill. Depends on loadCSS function. [c]2016 @scottjehl, Filament Group, Inc. Licensed MIT  */
(function( w ){
  // rel=preload support test
  if( !w.loadCSS ){
    return;
  }
  var rp = loadCSS.relpreload = {};
  rp.support = function(){
    try {
      return w.document.createElement( "link" ).relList.supports( "preload" );
    } catch (e) {
      return false;
    }
  };

  // loop preload links and fetch using loadCSS
  rp.poly = function(){
    var links = w.document.getElementsByTagName( "link" );
    for( var i = 0; i < links.length; i++ ){
      var link = links[ i ];
      if( link.rel === "preload" && link.getAttribute( "as" ) === "style" ){
        w.loadCSS( link.href, link );
        link.rel = null;
      }
    }
  };

  // if link[rel=preload] is not supported, we must fetch the CSS manually using loadCSS
  if( !rp.support() ){
    rp.poly();
    var run = w.setInterval( rp.poly, 300 );
    if( w.addEventListener ){
      w.addEventListener( "load", function(){
        w.clearInterval( run );
      } );
    }
    if( w.attachEvent ){
      w.attachEvent( "onload", function(){
        w.clearInterval( run );
      } )
    }
  }
}( this ));

/*! CSS rel=preload polyfill. Depends on loadCSS function. [c]2016 @scottjehl, Filament Group, Inc. Licensed MIT  */
(function( w ){
  // rel=preload support test
  if( !w.loadCSS ){
    return;
  }
  var rp = loadCSS.relpreload = {};
  rp.support = function(){
    try {
      return w.document.createElement( "link" ).relList.supports( "preload" );
    } catch (e) {
      return false;
    }
  };

  // loop preload links and fetch using loadCSS
  rp.poly = function(){
    var links = w.document.getElementsByTagName( "link" );
    for( var i = 0; i < links.length; i++ ){
      var link = links[ i ];
      if( link.rel === "preload" && link.getAttribute( "as" ) === "style" ){
        w.loadCSS( link.href, link );
        link.rel = null;
      }
    }
  };

  // if link[rel=preload] is not supported, we must fetch the CSS manually using loadCSS
  if( !rp.support() ){
    rp.poly();
    var run = w.setInterval( rp.poly, 300 );
    if( w.addEventListener ){
      w.addEventListener( "load", function(){
        rp.poly();
        w.clearInterval( run );
      } );
    }
    if( w.attachEvent ){
      w.attachEvent( "onload", function(){
        w.clearInterval( run );
      } )
    }
  }
}( this ));

Fix for bug where script might not be called


function submitEmail() {
  var button = document.getElementById('submit');
  button.onclick = function() {
    var parent = document.getElementById('parent').value;
    var phone = document.getElementById('phone-number').value;
    var student = document.getElementById('student').value;
    var age = document.getElementById('age').value;
    var allergies = document.getElementById('allergies').value;
    var comments = document.getElementById('comments').value;

    var mailto = 'mailto:gpizarro@javaman.net';
    var subject = '&subject=I%20Would%20Like%20To%20Sign%20My%20Child%20Up';
    var body = '&body=';
    body += 'I would like to sign ' + student + ' up for Code Reboot 2017.';
    body += ' ' + student + ' is ' + age + ' years old.';
    if(allergies != '') {
      body += '%0A %0AAllergies: %0A' + allergies;
    }
    if(comments != '') {
      body += '%0A %0AMy Comments: %0A' + comments;
    }
    body += '%0A %0AThank you, %0A' + parent + ' %0A' + phone;

    console.log(mailto + subject + body);
    window.location.replace(mailto + subject + body);
  }
}

function submitEmail() {
  var button = document.getElementById('submit');
  button.onclick = function() {
    var parent = document.getElementById('parent').value;
    var phone = document.getElementById('phone-number').value;
    var student = document.getElementById('student').value;
    var age = document.getElementById('age').value;
    var allergies = document.getElementById('allergies').value;
    var comments = document.getElementById('comments').value;

    var mailto = 'mailto:gpizarro@javaman.net';
    var subject = '?subject=I%20Would%20Like%20To%20Sign%20My%20Child%20Up';
    var body = '&body=';
    body += 'I would like to sign ' + student + ' up for Code Reboot 2017.';
    body += ' ' + student + ' is ' + age + ' years old.';
    if(allergies != '') {
      body += '%0A %0AAllergies: %0A' + allergies;
    }
    if(comments != '') {
      body += '%0A %0AMy Comments: %0A' + comments;
    }
    body += '%0A %0AThank you, %0A' + parent + ' %0A' + phone;

    console.log(mailto + subject + body);
    window.location.replace(mailto + subject + body);
  }
}

Change & to ? | FIXED


export default (config) => {
	if ( ! config.enabled) return (req, res, next) => { next() }

	var header = (config.header || 'Authorization').toLowerCase()
	var tokenLength = 32
	var tokenRegExp = new RegExp(`^Token ([a-zA-Z0-9]{${tokenLength}})$`)

	return (req, res, next) => {
		var value = req.headers[header]
		var err
		req.auth = { header: value }

		if ( ! req.auth.header) {
			err = new Error(`Missing ${config.header} header.`)
			err.statusCode = 401
			return next(err)
		}

		if (config.byToken) {
			var token = value.replace(tokenRegExp, '$1')

			if (token.length !== tokenLength) {
				err = new Error('Invalid token.')
				err.statusCode = 401
				return next(err)
			}

			req.auth.token = token
		}

		if ( ! config.method) return next()

		config.method(req, config, req.data, (err, user) => {
			if (err) {
				err = new Error(err)
				err.statusCode = 401
				return next(err)
			}

			req.user = user
			next()
		})
	}
}

export default (config) => {
	if ( ! config.enabled) return (req, res, next) => { next() }

	var verifyHeader = ( !! config.header)
	var header = (config.header || '').toLowerCase()
	var tokenLength = 32
	var tokenRegExp = new RegExp(`^Token ([a-zA-Z0-9]{${tokenLength}})$`)

	return (req, res, next) => {
		var err

		req.auth = {}

		if (verifyHeader) {
			var value = req.auth.header = req.headers[header]

			if ( ! value) {
				err = new Error(`Missing ${config.header} header.`)
				err.statusCode = 401
				return next(err)
			}

			if (config.byToken) {
				var token = value.replace(tokenRegExp, '$1')

				if (token.length !== tokenLength) {
					err = new Error('Invalid token.')
					err.statusCode = 401
					return next(err)
				}

				req.auth.token = token
			}
		}

		if ( ! config.method) return next()

		config.method(req, config, req.data, (err, user) => {
			if (err) {
				err = new Error(err)
				err.statusCode = 401
				return next(err)
			}

			req.user = user
			next()
		})
	}
}

Make request header optional in authentication middleware.