var express  = require('express'),
    initialize = require('express-initializers'),
    logger = require('./logger'),

    app = express();

logger.profile('startup');
// Let the initializers run
initialize(app)
    .catch(function (err) {
        logger.error('Unable to initialize app: ' + err.message);
        logger.error(err.stack);
    })
    .finally(function () {
        logger.profile('startup');
    });

module.exports = app;

var http = require('http'),
    express  = require('express'),
    initialize = require('express-initializers'),
    logger = require('./logger'),

    app = express();

// Increase the global maxSockets value
http.globalAgent.maxSockets = 1000;

logger.profile('startup');
// Let the initializers run
initialize(app)
    .catch(function (err) {
        logger.error('Unable to initialize app: ' + err.message);
        logger.error(err.stack);
    })
    .finally(function () {
        logger.profile('startup');
    });

module.exports = app;

Increase global http connection limit


"use strict";
var search = require('../lib/search');

module.exports = function (input, callback) {
    if (typeof input.query.plan !== 'undefined' && typeof input.query.plan.esquery !== 'undefined') {
        input.query.plan.esquery.from = input.query.offset || 0;
        if (input.query.plan.esonly) {
            input.query.plan.esquery.size = input.query.perpage;
        } else {
            input.query.plan.esquery.size = Math.max(1000, input.query.perpage * 4);
        }
    }
    search(input.query, function (results) {
        if (results.records) {
            callback({
                records: results.records,
                count: results.count,
                summary: 'Search: ' + input.query.canonical
            });
        } else if (results.pipe) {
            var records = results.pipe.range(input.query.offset, input.query.offset + input.query.size).toJSON();
            var count = input.query.offset + records.length;
            if (records.length > input.query.size) {
                records.pop();
            }
            callback({
                records: records,
                count: count,
                summary: 'Search: ' + input.query.canonical
            });
        }
    });
};

module.exports.message = 'search';

"use strict";
var search = require('../lib/search');

module.exports = function (input, callback) {
    if (typeof input.query.plan !== 'undefined' && typeof input.query.plan.esquery !== 'undefined') {
        input.query.plan.esquery.from = input.query.offset || 0;
        if (input.query.plan.esonly) {
            input.query.plan.esquery.size = input.query.perpage;
        } else {
            input.query.plan.esquery.size = Math.max(1000, input.query.perpage * 50);
        }
    }
    search(input.query, function (results) {
        if (results.records) {
            callback({
                records: results.records,
                count: results.count,
                summary: 'Search: ' + input.query.canonical
            });
        } else if (results.pipe) {
            var records = results.pipe.range(input.query.offset, input.query.offset + input.query.size).toJSON();
            var count = input.query.offset + records.length;
            if (records.length > input.query.size) {
                records.pop();
            }
            callback({
                records: records,
                count: count,
                summary: 'Search: ' + input.query.canonical
            });
        }
    });
};

module.exports.message = 'search';

Use 50 times the page size for faceting


/**
 * @file Chain agent interfacing with Etherscan.
 * @module chain/etherscan
 */

'use strict'

// Imports
const config = require('config')

/**
 * Private members store.
 * @private
 */
const privs = new WeakMap()

/**
 * Chain agent interfacing with Etherscan.
 * @static
 */
class ChainAgent {
  /**
   * No parameters.
   */
  constructor () {
    const priv = {}
    privs.set(this, priv)
    priv.apiKey = config.get('Etherscan.apiKey')
  }
}

// Expose
module.exports = ChainAgent

/**
 * @file Chain agent interfacing with Etherscan.
 * @module chain/etherscan
 */

'use strict'

/**
 * Private members store.
 * @private
 */
const privs = new WeakMap()

/**
 * Chain agent interfacing with Etherscan.
 * @static
 */
class ChainAgent {
  /**
   * No parameters.
   */
  constructor () {
    const priv = {}
    privs.set(this, priv)
  }
}

// Expose
module.exports = ChainAgent

Remove client logic from Etherscan chain agent


'use strict';
const gulp = require('gulp');
const shell = require('shelljs');
const size = require('gulp-size');
const argv = require('yargs').argv;

// 'gulp jekyll:tmp' -- copies your Jekyll site to a temporary directory
// to be processed
gulp.task('site:tmp', () =>
  gulp.src(['src/**/*', '!src/assets/**/*', '!src/assets'])
    .pipe(gulp.dest('.tmp/src'))
    .pipe(size({title: 'Jekyll'}))
);

// 'gulp jekyll' -- builds your site with development settings
// 'gulp jekyll --prod' -- builds your site with production settings
gulp.task('site', done => {
  if (!argv.prod) {
    shell.exec('jekyll build');
    done();
  } else if (argv.prod) {
    shell.exec('jekyll build --config _config.yml,_config.build.yml');
    done();
  }
});

// 'gulp doctor' -- literally just runs jekyll doctor
gulp.task('site:doctor', done => {
  shell.exec('jekyll doctor');
  done();
});

'use strict';
const gulp = require('gulp');
const shell = require('shelljs');
const size = require('gulp-size');
const argv = require('yargs').argv;

// 'gulp jekyll:tmp' -- copies your Jekyll site to a temporary directory
// to be processed
gulp.task('site:tmp', () =>
  gulp.src(['src/**/*', '!src/assets/**/*', '!src/assets'])
    .pipe(gulp.dest('.tmp/src'))
    .pipe(size({title: 'Jekyll'}))
);

// 'gulp jekyll' -- builds your site with development settings
// 'gulp jekyll --prod' -- builds your site with production settings
gulp.task('site', done => {
  if (!argv.prod) {
    shell.exec('jekyll build');
    done();
  } else if (argv.prod) {
    shell.exec('jekyll build --config _config.yml,_config.build.yml');
    done();
  }
});

// 'gulp doctor' -- literally just runs jekyll doctor
gulp.task('site:check', done => {
  shell.exec('jekyll doctor');
  done();
});

Fix site:doctor to work with generator-statisk


import React from 'react'
import { Provider } from 'rebass'
import { Head } from 'react-static'
import Nav from '../components/Nav'
import Bubbles from '../components/Bubbles'
import Stripe from '../components/Stripe'
import Features from '../components/Features'
import Superpower from '../components/Superpower'
import Collage from '../components/Collage'
import Mosaic from '../components/Mosaic'
import Start from '../components/Start'
import Footer from '../components/Footer'
import theme from '../theme'

export default () => (
  <Provider theme={theme}>
    <Head><title>Hack Club</title></Head>
    <Nav style={{ position: 'absolute', top: 0 }} cloud />
    <Bubbles />
    <Stripe />
    <Features />
    <Superpower />
    <Collage />
    <Mosaic />
    <Start />
    <Footer />
  </Provider>
)

import React from 'react'
import { Provider } from 'rebass'
import { Head } from 'react-static'
import Nav from '../components/Nav'
import Bubbles from '../components/Bubbles'
import Stripe from '../components/Stripe'
import Features from '../components/Features'
import Superpower from '../components/Superpower'
import Collage from '../components/Collage'
import Mosaic from '../components/Mosaic'
import Start from '../components/Start'
import Footer from '../components/Footer'
import theme from '../theme'

export default () => (
  <Provider theme={theme}>
    <Head><title>Hack Club</title></Head>
    <Nav style={{ position: 'absolute', top: 0 }} cloud="true" />
    <Bubbles />
    <Stripe />
    <Features />
    <Superpower />
    <Collage />
    <Mosaic />
    <Start />
    <Footer />
  </Provider>
)

Fix the cloud type warning on build


IRG.constants = (function(){

    var approvedDomains = function(){
        return ["imgur.com", "flickr.com", "cdn.diycozyhome.com", "pbs.twimg.com", "msnbc.com",
            "fbcdn-sphotos-f-a.akamaihd.net", "flic.kr", "instagram.com", "deviantart.com",
            "s-media-cache-ak0.pinimg.com", "gfycat.com", "gifbeam.com", "staticflickr.com", "imgflip.com",
            "upload.wikimedia.org", "livememe.com", "tumblr.com", "wikipedia.org", "archive.is", "imgrush.com",
            "deviantart.net", "awwni.me"];
    };

    var messages = {
        error: {
            aProblem : "There's been a problem!",
            noImagesFound: "No images were found."
        },
        info: {
            enterSubreddits: "Please enter your subreddits."
        }
    };

    return{
        approvedDomains: approvedDomains(),
        messages: messages
    };
})();

IRG.config = (function(){

})();

IRG.constants = (function(){

    var approvedDomains = function(){
        return ["imgur.com", "flickr.com", "cdn.diycozyhome.com", "pbs.twimg.com", "msnbc.com",
            "fbcdn-sphotos-f-a.akamaihd.net", "flic.kr", "instagram.com", "deviantart.com",
            "s-media-cache-ak0.pinimg.com", "gfycat.com", "gifbeam.com", "staticflickr.com", "imgflip.com",
            "upload.wikimedia.org", "livememe.com", "tumblr.com", "wikipedia.org", "archive.is", "imgrush.com",
            "deviantart.net", "awwni.me", "i."];
    };

    var messages = {
        error: {
            aProblem : "There's been a problem!",
            noImagesFound: "No images were found."
        },
        info: {
            enterSubreddits: "Please enter your subreddits."
        }
    };

    return{
        approvedDomains: approvedDomains(),
        messages: messages
    };
})();

IRG.config = (function(){

})();

Add the i. domain under which a lot of websites host their images


/**
 * Copyright (c) 2014 TopCoder, Inc. All rights reserved.
 */
'use strict';

var postgresql = require('postgresql-sequelize');
var Sequelize = postgresql.Sequelize;
var sequelize = postgresql.sequelize;

sequelize.define('challenge', {
  regStartDate: Sequelize.DATE,
  subEndDate: Sequelize.DATE,
  title: {
    type: Sequelize.STRING(128),
    validate: {
      notNull: true,
      notEmpty: true
    }
  },
  type: Sequelize.STRING(32),
  overview: Sequelize.STRING(140),
  description: Sequelize.TEXT,
  registeredDescription: Sequelize.TEXT,
  tags: Sequelize.ARRAY(Sequelize.TEXT)
},
{tableName: 'challenges'});

// sync with database
sequelize
  .sync({force: false})
  .complete(function(err) {
    if (!!err) {
      console.log('An error occurred while syncing database:', err);
    } else {
      console.log('Database is synced!');
    }
  });

Remove database sync by sequalize


$(function() {
  $(document).on('keyup', function(event) {
    var $panel          = $('.snapshot-review-panel'),
        $activeSnapshot = $panel.find('.snapshot-card-active'),
        $goTo;
    if (event.which == 37) { // Left arrow
      $goTo = $activeSnapshot.prev();
    } else if (event.which == 39) { // Right arrow
      $goTo = $activeSnapshot.next();
    } else {
      return;
    }

    if ($goTo.length) {
      Turbolinks.visit($goTo.find('a').first().attr('href'));
    }
  });
});

$(function() {
  $(document).on('keyup', function(event) {
    var $panel          = $('.snapshot-review-panel'),
        $activeSnapshot = $panel.find('.snapshot-card-active'),
        $goTo;
    if (event.which == 37) { // Left arrow
      $goTo = $activeSnapshot.prev();
    } else if (event.which == 39) { // Right arrow
      $goTo = $activeSnapshot.next();
    } else {
      return;
    }

    if ($goTo.length) {
      Turbolinks.visit($goTo.attr('href'));
    }
  });
});

Fix keyboard navigation on snapshot page


import columnUtils from './columnUtils';
import compositionUtils from './compositionUtils';
import dataUtils from './dataUtils';
import rowUtils from './rowUtils';
import sortUtils from './sortUtils';

export default {
  columnUtils,
  compositionUtils,
  dataUtils,
  rowUtils,
  sortUtils,
};

import * as columnUtils from './columnUtils';
import * as compositionUtils from './compositionUtils';
import * as dataUtils from './dataUtils';
import * as rowUtils from './rowUtils';
import * as sortUtils from './sortUtils';

export default {
  columnUtils,
  compositionUtils,
  dataUtils,
  rowUtils,
  sortUtils,
};

Fix utils export from module


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

import Octicon from '../../views/octicon';

export class Commit extends React.Component {
  static propTypes = {
    item: PropTypes.object.isRequired,
  }

  render() {
    const commit = this.props.item;
    return (

        <Octicon className="pre-timeline-item-icon" icon="git-commit" />

        {commit.oid.slice(0, 8)}

    );
  }
}

export default Relay.createContainer(Commit, {
  fragments: {
    item: () => Relay.QL`
      fragment on Commit {
        author {
          name avatarUrl
          user {
            login
          }
        }
        oid messageHeadline messageHeadlineHTML
      }
    `,
  },
});

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

import Octicon from '../../views/octicon';

export class Commit extends React.Component {
  static propTypes = {
    item: PropTypes.object.isRequired,
  }

  render() {
    const commit = this.props.item;
    return (

        <Octicon className="pre-timeline-item-icon" icon="git-commit" />

        {commit.oid.slice(0, 8)}

    );
  }
}

export default Relay.createContainer(Commit, {
  fragments: {
    item: () => Relay.QL`
      fragment on Commit {
        author {
          name avatarUrl
          user {
            login
          }
        }
        oid message messageHeadlineHTML
      }
    `,
  },
});

Set full commit message as tooltip


/**
 * @format
 */

import {Navigation} from 'react-native-navigation';
import HomeScreen from './screens/Home';
import DetailsScreen from './screens/Details';
import {NativeModules} from 'react-native';
import Bugsnag from '@bugsnag/react-native';
import BugsnagReactNativeNavigation from '@bugsnag/plugin-react-native-navigation';

NativeModules.BugsnagTestInterface.startBugsnag({
  apiKey: '12312312312312312312312312312312',
  endpoint: 'http://bs-local.com:9339',
  autoTrackSessions: false
}, () => {
  Bugsnag.start({
    plugins: [new BugsnagReactNativeNavigation(Navigation)],
  })
})

Navigation.registerComponent('Home', () => HomeScreen);
Navigation.registerComponent('Details', () => DetailsScreen);
Navigation.events().registerAppLaunchedListener(async () => {
  Navigation.setRoot({
    root: {
      stack: {
        children: [
          {
            component: {
              name: 'Home',
            },
          },
        ],
      },
    },
  })
})

/**
 * @format
 */

import {Navigation} from 'react-native-navigation';
import HomeScreen from './screens/Home';
import DetailsScreen from './screens/Details';
import {NativeModules} from 'react-native';
import Bugsnag from '@bugsnag/react-native';
import BugsnagReactNativeNavigation from '@bugsnag/plugin-react-native-navigation';

NativeModules.BugsnagTestInterface.startBugsnag({
  apiKey: '12312312312312312312312312312312',
  endpoint: 'http://bs-local.com:9339',
  autoTrackSessions: false
}).then(() => {
  Bugsnag.start({
    plugins: [new BugsnagReactNativeNavigation(Navigation)],
  })
})

Navigation.registerComponent('Home', () => HomeScreen);
Navigation.registerComponent('Details', () => DetailsScreen);
Navigation.events().registerAppLaunchedListener(async () => {
  Navigation.setRoot({
    root: {
      stack: {
        children: [
          {
            component: {
              name: 'Home',
            },
          },
        ],
      },
    },
  })
})

Use promise instead of callback for starting Bugsnag


pageflow.FileSettingsDialogView = Backbone.Marionette.ItemView.extend({
  template: 'templates/file_settings_dialog',
  className: 'file_settings_dialog editor dialog',

  mixins: [pageflow.dialogView],

  ui: {
    content: '.content'
  },

  onRender: function() {
    this.tabsView = new pageflow.TabsView({
      model: this.model,
      i18n: 'pageflow.editor.files.settings_dialog_tabs',
      defaultTab: this.options.tabName
    });

    _.each(this.model.fileType().settingsDialogTabs, function(options) {
      this.tabsView.tab(options.name, _.bind(function() {
        return this.subview(new options.view({
          model: this.model
        }));
      }, this));
    }, this);

    this.ui.content.append(this.subview(this.tabsView).el);
  }
});

pageflow.FileSettingsDialogView.open = function(options) {
  pageflow.app.dialogRegion.show(new pageflow.FileSettingsDialogView(options));
};

pageflow.FileSettingsDialogView = Backbone.Marionette.ItemView.extend({
  template: 'templates/file_settings_dialog',
  className: 'file_settings_dialog editor dialog',

  mixins: [pageflow.dialogView],

  ui: {
    content: '.content'
  },

  onRender: function() {
    this.tabsView = new pageflow.TabsView({
      model: this.model,
      i18n: 'pageflow.editor.files.settings_dialog_tabs',
      defaultTab: this.options.tabName
    });

    _.each(this.model.fileType().settingsDialogTabs, function(options) {
      this.tabsView.tab(options.name, _.bind(function() {
        return this.subview(new options.view(
          _.extend({model: this.model}, options.viewOptions)));
      }, this));
    }, this);

    this.ui.content.append(this.subview(this.tabsView).el);
  }
});

pageflow.FileSettingsDialogView.open = function(options) {
  pageflow.app.dialogRegion.show(new pageflow.FileSettingsDialogView(options));
};

Allow passing viewOptions to FileSettingsDialogTabs


module.exports = {

  env: {

    'browser': false

  }

};

module.exports = {

  env: {

    'browser': false

  },

  rules: {

    'no-console': ['off']

  }

};

Allow console statements in server code


import queryString from "query-string";

class Api {
    static get(url, data = {}) {
        return this.request(url + (Object.keys(data).length > 0 ? ('?' + queryString.stringify(data)) : '' ), undefined, "GET");
    }

    static post(url, data = {}) {
        return this.request(url, data, "POST");
    }

    static delete(url, data = {}) {
        return this.request(url, data, "DELETE");
    }

    static patch(url, data = {}) {
        return this.request(url, data, "PATCH");
    }

    static request(url, data, method) {
        let req = fetch("http://" + window.location.hostname + ':8080/api/' + url, {
            method: method,
            headers: {
                'content-type': 'application/json'
            },
            body: JSON.stringify(data)
        });

        return new Promise((resolve, reject) => {
            req.then((response) => {
                if (response.ok) {
                    response.json().then((data) => {
                        if (data.success) {
                            resolve(data.data);
                        } else {
                            let e = new Error(data.message);
                            e.response = response;
                            reject(e);
                        }
                    })
                } else {
                    let e = new Error(response.statusText);
                    e.response = response;
                    reject(e);
                }
            }).catch((e) => {
                reject(new Error(e));
            })
        });
    }
}

export default Api;

import queryString from "query-string";

class Api {
    static get(url, data = {}) {
        return this.request(url + (Object.keys(data).length > 0 ? ('?' + queryString.stringify(data)) : '' ), undefined, "GET");
    }

    static post(url, data = {}) {
        return this.request(url, data, "POST");
    }

    static delete(url, data = {}) {
        return this.request(url, data, "DELETE");
    }

    static patch(url, data = {}) {
        return this.request(url, data, "PATCH");
    }

    static request(url, data, method) {
        let req = fetch("http://" + window.location.hostname + ':8080/api/' + url, {
            method: method,
            headers: {
                'content-type': 'application/json'
            },
            body: JSON.stringify(data)
        });

        return new Promise((resolve, reject) => {
            req.then((response) => {
                if (response.ok) {
                    response.json().then((data) => {
                        if (data.success) {
                            resolve(data.data);
                        } else {
                            let e = new Error(data.message);
                            e.response = response;
                            reject(e);
                        }
                    }).catch(() => {
                        let e = new Error('Malformed response');
                        e.response = response;
                        reject(e);
                    })
                } else {
                    let e = new Error(response.statusText);
                    e.response = response;
                    reject(e);
                }
            }).catch((e) => {
                reject(new Error(e));
            })
        });
    }
}

export default Api;

Add handling for invalid JSON returned


import update from 'react-addons-update';

var dancers = [
  {
    position: {
      top: 200, left: 90
    },
    name: 'Ryan'
  },
  {
    position: {
      top: 100, left: 20
    },
    name: 'Yip'
  }
];
var newDancers = update(dancers, {$merge: {}});
var formations = [dancers, newDancers];

export var initialState = {
  formations,
  selectedDancer: 0,
  editing: 0
};

import update from 'react-addons-update';

var formation1 = [
  {
    dancer: 0,
    position: {
      top: 200,
      left: 90
    }
  }, {
    dancer: 1,
    position: {
      top: 100,
      left: 20
    }
  }];

var formation2 = update(formation1, {$merge: {}});
var formations = [formation1, formation2];

var initialState = {
  formations,
  selectedDancer: 0,
  editing: 0,
  dancers: ['ryan', 'yip']
};

export default initialState;

Change dancers to be its own list


"use strict";

var gulp = require("gulp")
  , $ = require("gulp-load-plugins")()
  , del = require("del")

gulp.task("img", function () {
  return gulp.src("img/*.sketch")
    .pipe($.sketch({
      export: "slices"
    , formats: [
        "png"
      , "svg"
      ]
    , scales: [
        "1.0"
      , "2.0"
      ]
    }))
    .pipe($.ignore.exclude(/@2x.svg$/))
    .pipe($.cache($.imagemin({
      svgoPlugins: [
        { removeTitle: true }
      , { removeDesc: true }
      ]
    })))
    .pipe(gulp.dest("dist"))
    .pipe($.size({
      showFiles: true
    }))
})

gulp.task("watch", function () {
  gulp.watch("img/*.sketch", ["img"])
})

"use strict";

var gulp = require("gulp")
  , $ = require("gulp-load-plugins")()
  , del = require("del")

gulp.task("img", function () {
  return gulp.src("img/*.sketch")
    .pipe($.sketch({
      export: "slices"
    , formats: [
        "png"
      , "svg"
      ]
    , scales: [
        "1.0"
      , "2.0"
      ]
    }))
    .pipe($.ignore.exclude(/@2x.svg$/))
    .pipe($.cache($.imagemin({
      svgoPlugins: [
        { removeTitle: true }
      , { removeDesc: true }
      ]
    })))
    .pipe(gulp.dest("dist"))
    .pipe($.size({
      showFiles: true
    }))
})

gulp.task("build", ["img"])

gulp.task("default", ["build"])

gulp.task("watch", function () {
  gulp.watch("img/*.sketch", ["img"])
})

Add `build` and `default` tasks.


/* @flow */

import express from 'express'
import expressWinston from 'express-winston'
import logger from 'winston'
import cors from 'cors'
import Path from 'path'

import {
  GaiaDiskReader
} from './server'

export function makeHttpServer(config: Object) {
  const app = express()
  const server = new GaiaDiskReader(config)

  app.config = config

  app.use(expressWinston.logger({
    transports: logger.loggers.default.transports }))

  app.use(cors())

  app.get(/\/([a-zA-Z0-9]+)\/(.+)/, (req: express.request, res: express.response) => {
    let filename = req.params[1]
    if (filename.endsWith('/')) {
      filename = filename.substring(0, filename.length - 1)
    }
    const address = req.params[0]

    return server.handleGet(address, filename)
      .then((fileInfo) => {
        const exists = fileInfo.exists
        const contentType = fileInfo.contentType

        if (!exists) {
          return res.status(404).send('File not found')
        }

        const opts = {
          root: config.storageRootDirectory,
          headers: {
            'content-type': contentType
          }
        }
        const path = Path.join(address, filename)

        return res.sendFile(path, opts)
      })
      .catch((err) => {
        logger.error(err)
        return res.status(400).send('Could not return file')
      })
  })

  return app
}

/* @flow */

import express from 'express'
import expressWinston from 'express-winston'
import logger from 'winston'
import cors from 'cors'
import Path from 'path'

import {
  GaiaDiskReader
} from './server'

export function makeHttpServer(config: Object) {
  const app = express()
  const server = new GaiaDiskReader(config)

  app.config = config

  app.use(expressWinston.logger({
    transports: logger.loggers.default.transports }))

  app.use(cors())

  app.get(/\/([a-zA-Z0-9]+)\/(.+)/, (req: express.request, res: express.response) => {
    let filename = req.params[1]
    if (filename.endsWith('/')) {
      filename = filename.substring(0, filename.length - 1)
    }
    const address = req.params[0]

    return server.handleGet(address, filename)
      .then((fileInfo) => {
        const exists = fileInfo.exists
        const contentType = fileInfo.contentType

        if (!exists) {
          return res.status(404).send('File not found')
        }

        const opts = {
          root: config.diskSettings.storageRootDirectory,
          headers: {
            'content-type': contentType
          }
        }
        const path = Path.join(address, filename)

        return res.sendFile(path, opts)
      })
      .catch((err) => {
        logger.error(err)
        return res.status(400).send('Could not return file')
      })
  })

  return app
}

Fix gaia-reader bug where wrong storageRootDirectory config key was being used in file GETs.


const indexer = require('../../src/Inverted-index');

describe('Inverted Index Tests', function() {
  let fileName = './books.json';

  beforeEach(function(done) {
    indexer.createIndex(fileName);
    done();
  });

  describe('Read book data', function() {
    it('asserts that the file content is actually a valid JSON Array', function() {
      expect(indexer.createIndex).toBeDefined();
    });
    it('asserts that json file is not empty', function() {
      expect(indexer.createIndex).not.toEqual([]);
    });
  });

  describe('Populate index', function() {
    it('ensures that the index is correct', function() {
      expect(indexer.getIndex.length).not.toBe(null);
    });

    it('verifies that the index is created once the JSON file has been read', function() {
      expect(indexer.getIndex).toBeDefined();
    });

    it('index maps the string keys to the correct objects in the JSON array', function() {
      expect(indexer.searchIndex(fileName, 'alice').alice).toEqual([1]);
      expect(indexer.searchIndex(fileName, 'ring').ring).toEqual([2]);
    });
  });
  describe('Search Index', function() {
    it('verifies that searching the index returns an array of the indices of the correct objects', function() {
      expect(indexer.searchIndex(fileName, 'alice').alice).toEqual([1]);
      expect(indexer.searchIndex(fileName, 'ring').ring).toEqual([2]);
    });

  });
});

const indexer = require('../../src/inverted-index');

describe('Inverted Index Tests', function() {
  let fileName = './books.json';

  beforeEach(function(done) {
    indexer.createIndex(fileName);
    done();
  });

  describe('Read book data', function() {
    it('asserts that the file content is actually a valid JSON Array', function() {
      expect(indexer.createIndex).toBeDefined();
    });
    it('asserts that json file is not empty', function() {
      expect(indexer.createIndex).not.toEqual([]);
    });
  });

  describe('Populate index', function() {
    it('ensures that the index is correct', function() {
      expect(indexer.getIndex.length).not.toBe(null);
    });

    it('verifies that the index is created once the JSON file has been read', function() {
      expect(indexer.getIndex).toBeDefined();
    });

    it('index maps the string keys to the correct objects in the JSON array', function() {
      expect(indexer.searchIndex(fileName, 'alice').alice).toEqual([1]);
      expect(indexer.searchIndex(fileName, 'ring').ring).toEqual([2]);
    });
  });
  describe('Search Index', function() {
    it('verifies that searching the index returns an array of the indices of the correct objects', function() {
      expect(indexer.searchIndex(fileName, 'alice').alice).toEqual([1]);
      expect(indexer.searchIndex(fileName, 'ring').ring).toEqual([2]);
    });

  });
});

Refactor - modify file require


import Ember from 'ember';

const [major, minor] = Ember.VERSION.split('.');
const isGlimmer = major > 2 || (major == 2 && minor >= 10); // >= 2.10

let hasBlockSymbol;

if (major == 3 && minor >= 1) {
  // do nothing since the symbol is not exported
} else if (isGlimmer) {
  hasBlockSymbol = Ember.__loader.require('ember-glimmer/component')[
    'HAS_BLOCK'
  ];
} else {
  hasBlockSymbol = Ember.__loader.require('ember-htmlbars/component')[
    'HAS_BLOCK'
  ];
}

// NOTE: I really don't know how to test this
export default function hasBlock(emberComponent) {
  // Since Glimmer moved to TypeScript, we can't get the symbol.
  // This is a terrible but working way to get the value.
  if (!hasBlockSymbol) {
    const regex = /HAS_BLOCK/;
    hasBlockSymbol = Object.keys(emberComponent).find(key => regex.test(key));
  }

  return Ember.get(emberComponent, hasBlockSymbol);
}

import Ember from 'ember';

const [major, minor] = Ember.VERSION.split('.');
const isGlimmer = major > 2 || (major == 2 && minor >= 10); // >= 2.10

let hasBlockSymbol;

if (major == 3 && minor >= 1) {
  // do nothing since the symbol is not exported
} else if (isGlimmer) {
  hasBlockSymbol = Ember.__loader.require('ember-glimmer/component')[
    'HAS_BLOCK'
  ];
} else {
  hasBlockSymbol = Ember.__loader.require('ember-htmlbars/component')[
    'HAS_BLOCK'
  ];
}

// NOTE: I really don't know how to test this
export default function hasBlock(emberComponent) {
  // Since Glimmer moved to TypeScript, we can't get the symbol.
  // This is a terrible but working way to get the value.
  if (!hasBlockSymbol) {
    const regex = /HAS_BLOCK/;
    hasBlockSymbol = Object.getOwnPropertyNames(emberComponent).find(key =>
      regex.test(key)
    );
  }

  return Ember.get(emberComponent, hasBlockSymbol);
}

Use `getOwnPropertyNames` instead of `keys` to retrieve list of keys


import React from 'react';
import PropTypes from 'prop-types';
import { TextInput, StyleSheet } from 'react-native';
import { DARKER_GREY, LIGHT_GREY } from '../../../globalStyles/colors';
import { APP_FONT_FAMILY } from '../../../globalStyles/fonts';
import { useJSONFormOptions } from '../JSONFormContext';

export const Text = ({ autofocus, disabled, placeholder, value, onChange }) => {
  const { focusController } = useJSONFormOptions();

  const ref = focusController.useRegisteredRef();

  return (
    <TextInput
      ref={ref}
      style={styles.textInputStyle}
      value={value}
      placeholderTextColor={LIGHT_GREY}
      underlineColorAndroid={DARKER_GREY}
      placeholder={placeholder}
      selectTextOnFocus
      returnKeyType="next"
      autoCapitalize="none"
      autoCorrect={false}
      onChangeText={onChange}
      onSubmitEditing={() => focusController.next(ref)}
      editable={!disabled}
      blurOnSubmit={false}
      autoFocus={autofocus}
    />
  );
};

const styles = StyleSheet.create({
  textInputStyle: { flex: 1, fontFamily: APP_FONT_FAMILY },
});

Text.propTypes = {
  autofocus: PropTypes.bool,
  disabled: PropTypes.bool,
  placeholder: PropTypes.string,
  value: PropTypes.string,
  onChange: PropTypes.func.isRequired,
};

Text.defaultProps = {
  autofocus: false,
  disabled: false,
  placeholder: '',
  value: '',
};

import React from 'react';
import PropTypes from 'prop-types';
import { TextInput, StyleSheet } from 'react-native';
import { DARKER_GREY, LIGHT_GREY } from '../../../globalStyles/colors';
import { APP_FONT_FAMILY } from '../../../globalStyles/fonts';
import { useJSONFormOptions } from '../JSONFormContext';

export const Text = ({ autofocus, disabled, placeholder, value, onChange }) => {
  const { focusController } = useJSONFormOptions();

  const ref = focusController.useRegisteredRef();

  return (
    <TextInput
      ref={ref}
      style={styles.textInputStyle}
      value={value}
      placeholderTextColor={LIGHT_GREY}
      underlineColorAndroid={DARKER_GREY}
      placeholder={placeholder}
      selectTextOnFocus
      returnKeyType="next"
      autoCapitalize="none"
      autoCorrect={false}
      onChangeText={newVal => onChange(newVal || undefined)}
      onSubmitEditing={() => focusController.next(ref)}
      editable={!disabled}
      blurOnSubmit={false}
      autoFocus={autofocus}
    />
  );
};

const styles = StyleSheet.create({
  textInputStyle: { flex: 1, fontFamily: APP_FONT_FAMILY },
});

Text.propTypes = {
  autofocus: PropTypes.bool,
  disabled: PropTypes.bool,
  placeholder: PropTypes.string,
  value: PropTypes.string,
  onChange: PropTypes.func.isRequired,
};

Text.defaultProps = {
  autofocus: false,
  disabled: false,
  placeholder: '',
  value: '',
};

Add setting of undefined rather than an empty string


const createHashTable = require('../DataStructures/HashTable')
/**
 * Given two arrays, find the difference between them.
 * Example:
 * [1, 2, 3] and [2, 4, 1] => [3, 4]
 * ['A', 'B'] and ['C', 'D', 'A', 'E'] => ['B', 'C', 'D', 'E']
 *
 * We can solve it by looping through all elements of both arrays
 * and keeping track of unique items in a hash table.
 * Resulting diff will be an array of unique items of both input arrays.
 *
 * Performance: O(n)
 * @param {Array} first
 * @param {Array} second
 */
module.exports = (first, second) => {
  const unique = createHashTable()
  const merged = []
  const merge = input => {
    for (let item of input) {
      merged.push(item)
      unique.set(item, !unique.get(item))
    }
  }

  merge(first)
  merge(second)

  return merged.filter(item => unique.get(item))
}

const createHashTable = require('../DataStructures/HashTable')
/**
 * Given two arrays, find the difference between them.
 * Example:
 * [1, 2, 3] and [2, 4, 1] => [3, 4]
 * ['A', 'B'] and ['C', 'D', 'A', 'E'] => ['B', 'C', 'D', 'E']
 *
 * We can solve it by looping through all elements of both arrays
 * and keeping track of unique items in a hash table.
 * Resulting diff will be an array of unique items of both input arrays.
 *
 * Performance: O(n)
 * @param {Array} first
 * @param {Array} second
 */
module.exports = (first, second) => {
  const unique = createHashTable()

  return first.concat(second)
    .map(item => {
      unique.set(item, !unique.get(item))
      return item
    })
    .filter(item => unique.get(item))
}

Use map instead of for loop.


var children = node.childNodes;
if (children.length === 0) { return false; }

for (var i = 0; i < children.length; i++) {
	if ([1, 3].indexOf(children[i].nodeType) === -1) { continue; }
	if (children[i].nodeType === 1 && children[i].nodeName !== 'LI') { return true; }
	if (children[i].nodeType === 3 && children[i].nodeValue.trim() !== '') { return true; }
}

return false;

var children = node.childNodes;
if (children.length === 0) { return false; }

for (var i = 0; i < children.length; i++) {
	if (children[i].nodeType === 1 && children[i].nodeName !== 'LI') { return true; }
	if (children[i].nodeType === 3 && children[i].nodeValue.trim() !== '') { return true; }
}

return false;

Handle bare text nodes in lists slightly better


define(['require', 'app', 'backbone.marionette', 'backbone', 'marked',
    'underscore', 'json!./keyboardShortcuts.json'],
  function (require, App, Marionette, Backbone, marked, _, keyboardShortcuts) {
    'use strict';

    var controller = {
      showHelp: function () {
        require(['./views/HelpView'],
          function (HelpView) {
            _.each(keyboardShortcuts, function (category) {
              _.each(category.shortcuts, function (shortcut) {
                shortcut.description = marked(shortcut.description);
              });
            });

            var helpModel = new Backbone.Model({keyboardShortcuts: keyboardShortcuts});
            App.mainRegion.show(new HelpView({model: helpModel}));
          });
      }
    };

    App.addInitializer(function () {
      new Marionette.AppRouter({
        controller: controller,
        appRoutes: {
          'help': 'showHelp'
        }
      });
    });
  });

define(['require', 'app', 'backbone.marionette', 'backbone', 'marked',
    'underscore', 'json!help/keyboardShortcuts.json'],
  function (require, App, Marionette, Backbone, marked, _, keyboardShortcuts) {
    'use strict';

    var controller = {
      showHelp: function () {
        require(['./views/HelpView'],
          function (HelpView) {
            _.each(keyboardShortcuts, function (category) {
              _.each(category.shortcuts, function (shortcut) {
                shortcut.description = marked(shortcut.description);
              });
            });

            var helpModel = new Backbone.Model({keyboardShortcuts: keyboardShortcuts});
            App.mainRegion.show(new HelpView({model: helpModel}));
          });
      }
    };

    App.addInitializer(function () {
      new Marionette.AppRouter({
        controller: controller,
        appRoutes: {
          'help': 'showHelp'
        }
      });
    });
  });

Fix 'Dynamic load not allowed: help/keyboardShortcuts.json' caused by using relative path


var searchApp = angular.module('searchApp', []);

searchApp.controller('SimpleController', function($scope){
  $scope.customers = [
  {name: 'Gendelman', city: 'West Bloomfield'},
  {name: 'Ryan Lesson', city: "Chicago"},
  {name: 'Corey Lesson', city: "Bloomfield Hills"}
  ];
});

var searchApp = angular.module('searchApp', []);

searchApp.config(function($routeProvider){
  $routeProvider
  .when('/',
  {
    controller: 'SimpleController',
    templateUrl: 'Partials/View1.html'
  })
  .when('/view2',
  {
    controller: 'SimpleController',
    templateUrl: "Partials/View2.html"
  })
  .otherwise({redirectTo: '/'});
});

searchApp.controller('SimpleController', function($scope){
  $scope.customers = [
  {name: 'Gendelman', city: 'West Bloomfield'},
  {name: 'Ryan Lesson', city: "Chicago"},
  {name: 'Corey Lesson', city: "Bloomfield Hills"},
  {name: "Ash Ketchum", city: "Pallet Town"},
  {name: "Videl", city: "Hercule City"},
  {name: "Josh Lesson", city: "Miami Beach"}
  ];

  $scope.addCustomer = function(){
    $scope.customers.push({name: $scope.newCustomer.name , city: $scope.newCustomer.city})
  }
});

Add route configuration for partials


var express = require('express');
var http = require('http');
var util = require('util');

var port = 8081;
var status;

var app = express();

app.post('/men', function(req, res) {
  if (req.body.status) {
    status = req.body.status;

    console.log('Room is ', status);
  } else {
    console.err('body didn\'t contain a status\n', req.body);
  }

  res.set('Content-Type', 'text/plain');
  res.send('ok');
});

app.post('/women', function(req, res) {
  res.send('hello world');
});

app.get('/', function(req, res) {

  // TODO: Serve web content
  res.send('hello world');
});

app.listen(port);

var express = require('express');
var http = require('http');
var util = require('util');
var bodyParser = require('body-parser')

var port = 8081;
var status;

var app = express();

app.use(bodyParser.urlencoded({ extended: false }))

app.post('/men', function(req, res) {
  if (req.body.status) {
    status = req.body.status;

    console.log('Room is ', status);
  } else {
    console.err('body didn\'t contain a status\n', req.body);
  }

  res.set('Content-Type', 'text/plain');
  res.send('ok');
});

app.post('/women', function(req, res) {
  res.send('hello world');
});

app.get('/', function(req, res) {

  // TODO: Serve web content
  res.send('hello world');
});

app.listen(port);

Add body-parser middleware to correctly parse urlencoded


/**
 * The Indonesian Forest Area layer module. (based on IdnPlantationsLayerBySpecies.js)
 * More info @ https://basecamp.com/3063126/projects/10726176/todos/303918841
 * @return IdnForestArea class (extends CartoDBLayerClass)
 */

//   'text!map/cartocss/IdnForestArea.cartocss'], function(CartoDBLayerClass, ) {

define([
  'abstract/layer/CartoDBLayerClass',
  'text!map/cartocss/IdnForestArea.cartocss'
], function(CartoDBLayerClass, IdnForestAreaCartocss) {

  'use strict';

  var IdnForestArea = CartoDBLayerClass.extend({

    options: {
      sql: 'SELECT the_geom_webmercator, fungsi_kws, legend as name, cartodb_id, \'{tableName}\' AS tablename, \'{tableName}\' AS layer, {analysis} AS analysis FROM {tableName}',
      cartocss: IdnForestAreaCartocss,
      infowindow: true,
      interactivity: 'cartodb_id, tablename, layer, name, analysis',
      analysis: true,
            },
  });

  return IdnForestArea;

});

/**
 * The Indonesian Forest Area layer module. (based on IdnPlantationsLayerBySpecies.js)
 * More info @ https://basecamp.com/3063126/projects/10726176/todos/303918841
 * @return IdnForestArea class (extends CartoDBLayerClass)
 */

//   'text!map/cartocss/IdnForestArea.cartocss'], function(CartoDBLayerClass, ) {

define([
  'abstract/layer/CartoDBLayerClass',
  'text!map/cartocss/IdnForestArea.cartocss'
], function(CartoDBLayerClass, IdnForestAreaCartocss) {

  'use strict';

  var IdnForestArea = CartoDBLayerClass.extend({

    options: {
      sql: 'SELECT the_geom_webmercator, fungsi_kws, legend as name, cartodb_id, \'{tableName}\' AS tablename, \'{tableName}\' AS layer, {analysis} AS analysis FROM {tableName}',
      cartocss: IdnForestAreaCartocss,
      infowindow: true,
      interactivity: 'cartodb_id, tablename, layer, name, analysis',
      analysis: false
    }
  });

  return IdnForestArea;

});

Disable analysis and subscription from the Indonesia Forest Area layer as requested


Fix loading of users in autocomplete


export const whiteListDomains = [
  'hush.com',
  'opayq.com',
  'mailhero.io',
  'mailinator.com',
  'sudomail.com',
  'maildrop.cc',
  'neverbox.com',
  'thrott.com',
  'gmial.com',
  'sharklasers.com',
  'spamgourmet.com',
  'guerrillamail.com',
  'urhen.com',
  'max-mail.com',
  'daymail.life',
  'cndps.com',
  'nicemail.pro',
  'its0k.com',
  'spamex.com',
  'zebra.email',
  'byom.de',
  'yeah.net',
  'nigge.rs',
  'xoxy.net',
  'uymail.com',
];

export default whiteListDomains;

export const whiteListDomains = [
  'hush.com',
  'opayq.com',
  'mailhero.io',
  'mailinator.com',
  'sudomail.com',
  'maildrop.cc',
  'neverbox.com',
  'thrott.com',
  'gmial.com',
  'sharklasers.com',
  'spamgourmet.com',
  'guerrillamail.com',
  'urhen.com',
  'max-mail.com',
  'daymail.life',
  'cndps.com',
  'nicemail.pro',
  'its0k.com',
  'spamex.com',
  'zebra.email',
  'byom.de',
  'yeah.net',
  'nigge.rs',
  'xoxy.net',
  'uymail.com',
  'mailhaven.com',
];

export default whiteListDomains;

Allow mailhaven as valid provider


const { PhoneNumberUtil } = require('google-libphonenumber');
const scheduledTasksSchemaFunc = require('./schemas/scheduled-tasks-schema');
const phoneUtil = PhoneNumberUtil.getInstance();
const validatePhoneNumber = (number) => {
  const parsedNumber = phoneUtil.parseAndKeepRawInput(number, 'KE');

  return phoneUtil.isValidNumber(parsedNumber);
};

module.exports = (sequelize, DataTypes) => {
  const overrides = {
    engineersPhoneNumber: {
      type: DataTypes.STRING,
      validate: {
        isValidPhoneNumber(value) {
          if (!validatePhoneNumber(value)) {
            throw new Error(`${value} is an invalid phone number.`);
          }
        }
      }
    },
    contactPersonsPhoneNumber: {
      type: DataTypes.STRING,
      allowNull: false,
      validate: {
        isValidPhoneNumber(value) {
          if (!validatePhoneNumber(value)) {
            throw new Error(`${value} is an invalid phone number.`);
          }
        }
      }
    }
  };

  const scheduledTasksSchema = Object.assign(
    {}, scheduledTasksSchemaFunc(DataTypes), overrides
  );

  const ScheduledTask = sequelize.define(
    'ScheduledTask',
    scheduledTasksSchema,
    {
      tableName: 'scheduled_tasks',
      classMethods: {
        associate: (models) => {
          ScheduledTask.belongsTo(models.User, {
            foreignKey: 'userId',
            onDelete: 'CASCADE'
          });
        }
      }
    }
  );

  return ScheduledTask;
};

const { PhoneNumberUtil } = require('google-libphonenumber');
const scheduledTasksSchemaFunc = require('./schemas/scheduled-tasks-schema');
const phoneUtil = PhoneNumberUtil.getInstance();
const validatePhoneNumber = (number) => {
  const parsedNumber = phoneUtil.parseAndKeepRawInput(number, 'KE');

  return phoneUtil.isValidNumber(parsedNumber);
};

module.exports = (sequelize, DataTypes) => {
  const overrides = {
    engineersPhoneNumber: {
      type: DataTypes.STRING,
      validate: {
        isValidPhoneNumber(value) {
          if (!validatePhoneNumber(value)) {
            throw new Error(`${value} is an invalid phone number.`);
          }
        }
      }
    },
    contactPersonsPhoneNumber: {
      type: DataTypes.STRING,
      allowNull: false,
      validate: {
        isValidPhoneNumber(value) {
          if (!validatePhoneNumber(value)) {
            throw new Error(`${value} is an invalid phone number.`);
          }
        }
      }
    },
    time: {
      type: DataTypes.TIME,
      allowNull: false,
      get() {
        return `${this.getDataValue('time')}hrs`;
      }
    }
  };

  const scheduledTasksSchema = Object.assign(
    {}, scheduledTasksSchemaFunc(DataTypes), overrides
  );

  const ScheduledTask = sequelize.define(
    'ScheduledTask',
    scheduledTasksSchema,
    {
      tableName: 'scheduled_tasks',
      classMethods: {
        associate: (models) => {
          ScheduledTask.belongsTo(models.User, {
            foreignKey: 'userId',
            onDelete: 'CASCADE'
          });
        }
      }
    }
  );

  return ScheduledTask;
};

Add a getter to scheduled task's time field


import { connect } from 'react-redux'
import {ensureState} from 'redux-optimistic-ui'
import Header from './Header'
import { requestPlay, requestPause } from 'routes/Queue/modules/queue'
import { requestPlayNext } from 'routes/Player/modules/player'

//  Object of action creators (can also be function that returns object).
const mapActionCreators = {
  requestPlay,
  requestPlayNext,
  requestPause,
}

const mapStateToProps = (state) => {
  state = ensureState(state)

  return {
    curId: state.queue.curId,
    curPos: state.queue.curPos,
    isPlaying: state.queue.isPlaying,
    isFinished: state.queue.isFinished,
    // library
    artistIds: state.library.artists.result,
    artists: state.library.artists.entities,
    songIds: state.library.songs.result,
    songs: state.library.songs.entities,
    // user
    user: state.account.user,
  }
}

export default connect(mapStateToProps, mapActionCreators)(Header)

import { connect } from 'react-redux'
import {ensureState} from 'redux-optimistic-ui'
import Header from './Header'
import { requestPlay, requestPause } from 'routes/Queue/modules/queue'
import { requestPlayNext } from 'routes/Player/modules/player'

//  Object of action creators (can also be function that returns object).
const mapActionCreators = {
  requestPlay,
  requestPlayNext,
  requestPause,
}

const mapStateToProps = (state) => {
  state = ensureState(state)

  return {
    curId: state.queue.curId,
    curPos: state.queue.curPos,
    isPlaying: state.queue.isPlaying,
    isFinished: state.queue.isFinished,
    errorMessage: state.errorMessage,
  }
}

export default connect(mapStateToProps, mapActionCreators)(Header)

Make global error msg available to header


var backandGlobal = {
    url: "http://localhost:4109/backapi", //"https://api.backand.com:8080",//
    defaultApp: null,
};

//load the backand banner
$(document).ready(function () {
    angular.element(document).ready(function () {
        backand.security.authentication.addLoginEvent();
        document.dispatchEvent(backand.security.authentication.onlogin);
    });
});

var zfill = function (num, len) { return (Array(len).join("0") + num).slice(-len); }

var backandGlobal = {
    url: "https://www.backand.com:8080",//
    defaultApp: null,
};

//load the backand banner
$(document).ready(function () {
    angular.element(document).ready(function () {
        backand.security.authentication.addLoginEvent();
        document.dispatchEvent(backand.security.authentication.onlogin);
    });
});

var zfill = function (num, len) { return (Array(len).join("0") + num).slice(-len); }

Move to production - DO NOT CHANGE


var assert = require("assert");
var fs = require("fs");
var html5Lint = require("html5-lint");

describe("Splash Page", function() {
    it("should have valid HTML", function(done) {
        fs.readFile("index.html", "utf8", function(err, html) {
            assert.ok(!err);
            html5Lint(html, function(err, results) {
                for (var m in results.messages) {
                    var msg = results.messages[m];
                    var type = msg.type; // error or warning
                    var message = msg.message;
                    console.log("HTML5 Lint [%s]: %s %s (line %s)", type, message, msg, msg.lastLine);
                    assert.notStrictEqual(type, "error", message + ", see line " + msg.lastLine + " in index.html");
                }
                done();
            });
        });
    });
});

var assert = require("assert");
var fs = require("fs");
var html5Lint = require("html5-lint");

var _html = "";

describe("index.html", function() {
    before(function() {
        _html = fs.readFileSync("index.html", "utf8");
    });
    it("should have valid HTML", function(done) {
        html5Lint(_html, function(err, results) {
            for (var m in results.messages) {
                var msg = results.messages[m];
                var type = msg.type; // error, warning, or info
                var message = msg.message;
                var lineNumber = msg.lastLine;
                console.log("HTML5 Lint [%s]: %s %s (line %s)", type, message, lineNumber);
                assert.notStrictEqual(type, "error", message + ", see line " + lineNumber + " in index.html");
            }
            done();
        });
    });
    it("should not contain tab characters", function() {
        assert.strictEqual(-1, _html.indexOf("\t"), "Please remove any tab indents from index.html");
    });
    it("should not contain single quote characters", function() {
        assert.strictEqual(-1, _html.indexOf("'"), "Please remove any single quote characters from index.html");
    });
});

Add tests for no \t and ' in index.html


'use strict';

var qs = require('querystring');

var request = require('request');

var dot = require('dot');
var hostHeader = dot.template('{{=it.username}}.localhost.lan');

var debug = require('../util/debug')('batch-client');

function BatchClient(endpoint, username, apiKey) {
    this.endpoint = endpoint;
    this.username = username;
    this.apiKey = apiKey;
}

module.exports = BatchClient;

BatchClient.prototype.enqueue = function(query, callback) {
    var enqueueRequest = {
        url: this.endpoint + '?' + qs.stringify({'api_key': this.apiKey}),
        headers: {
            'Content-Type': 'application/json',
            'Host': hostHeader({username: this.username}),
            'User-Agent': 'CartoDB-Analysis-API'
        },
        body: JSON.stringify({query: query})
    };
    request.post(enqueueRequest, function(err, response, body) {
        response = response || {};
        if (err || response.statusCode !== 201) {
            return callback(new Error('Unable to enqueue SQL API batch job'));
        }

        var job = JSON.parse(body);
        debug('Queued job: %j', job);

        return callback(null, {status: 'ok'});
    });
};

'use strict';

var qs = require('querystring');

var request = require('request');

var dot = require('dot');
var hostHeader = dot.template('{{=it.username}}.localhost.lan');

var debug = require('../util/debug')('batch-client');

function BatchClient(endpoint, username, apiKey) {
    this.endpoint = endpoint;
    this.username = username;
    this.apiKey = apiKey;
}

module.exports = BatchClient;

BatchClient.prototype.enqueue = function(query, callback) {
    var enqueueRequest = {
        url: this.endpoint + '?' + qs.stringify({'api_key': this.apiKey}),
        headers: {
            'Content-Type': 'application/json',
            'Host': hostHeader({username: this.username}),
            'User-Agent': 'camshaft'
        },
        body: JSON.stringify({query: query})
    };
    request.post(enqueueRequest, function(err, response, body) {
        response = response || {};
        if (err || response.statusCode !== 201) {
            return callback(new Error('Unable to enqueue SQL API batch job'));
        }

        var job = JSON.parse(body);
        debug('Queued job: %j', job);

        return callback(null, {status: 'ok'});
    });
};

Change user agent for batch api client


"use strict";

module.exports = function(ecs, data) {
	ecs.addEach(function(entity, elapsed) { // jshint ignore:line
		if (entity.center.x) {
			entity.position.x = Math.floor(data.canvas.width / 2);
			if (entity.size) {
				entity.position.x -= Math.floor(entity.size.width / 2);
			}
		}
		if (entity.center.y) {
			entity.position.y = Math.floor(data.canvas.height / 2);
			if (entity.size) {
				entity.position.y -= Math.floor(entity.size.height / 2);
			}
		}
	}, ["position", "center"]);
};

"use strict";

module.exports = function(ecs, data) {
	ecs.addEach(function(entity, elapsed) { // jshint ignore:line
		// FIXME: doesn't work with cameras yet.
		if (entity.center.x) {
			entity.position.x = Math.floor(data.canvas.width / 2);
			if (entity.size) {
				entity.position.x -= Math.floor(entity.size.width / 2);
			}
		}
		if (entity.center.y) {
			entity.position.y = Math.floor(data.canvas.height / 2);
			if (entity.size) {
				entity.position.y -= Math.floor(entity.size.height / 2);
			}
		}
	}, ["position", "center"]);
};

Add helpful comment for future me.


// Copyright 2014, Renasar Technologies Inc.
/* jshint: node:true */

'use strict';

var di = require('di');

module.exports = loggingProtocolFactory;

di.annotate(loggingProtocolFactory, new di.Provide('Protocol.Logging'));
di.annotate(loggingProtocolFactory,
    new di.Inject(
        'Assert',
        'Constants',
        'Services.Messenger',
        '_'
    )
);

function loggingProtocolFactory (assert, Constants, messenger, _) {
    var levels = _.keys(Constants.Logging.Levels);

    function LoggingProtocol() {
    }

    LoggingProtocol.prototype.publishLog = function (level, data) {
        assert.isIn('level', levels);
        assert.object(data, 'data');

        messenger.publish(
            Constants.Protocol.Exchanges.Logging.Name,
            level,
            data
        );
    };

    return new LoggingProtocol();
}

// Copyright 2014, Renasar Technologies Inc.
/* jshint: node:true */

'use strict';

var di = require('di');

module.exports = loggingProtocolFactory;

di.annotate(loggingProtocolFactory, new di.Provide('Protocol.Logging'));
di.annotate(loggingProtocolFactory,
    new di.Inject(
        'Assert',
        'Constants',
        'Services.Messenger',
        '_'
    )
);

function loggingProtocolFactory (assert, Constants, messenger, _) {
    var levels = _.keys(Constants.Logging.Levels);

    function LoggingProtocol() {
    }

    LoggingProtocol.prototype.publishLog = function (level, data) {
        assert.isIn(level, levels);
        assert.object(data, 'data');

        messenger.publish(
            Constants.Protocol.Exchanges.Logging.Name,
            level,
            data
        );
    };

    return new LoggingProtocol();
}

Fix logger level isIn bug


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

import { VegaChart, getVegaTheme } from 'widget-editor';

// /widget_data.json?widget_id=
class WidgetBlock extends React.Component {
  constructor(props) {
    super(props);
    this.widgetConfig = null;
    this.state = {
      loading: true,
      widget: null
    };
  }

  componentWillMount() {
    const { item } = this.props;
    this.getChart(item.content.widgetId);
  }

  shouldComponentUpdate(nextProps, nextState) {
    // This fixes a bug where the widgets would reload when hovering them
    return this.state !== nextState
      || this.props.item.content.widgetId !== nextProps.item.content.widgetId;
  }

  getChart(widgetId) {
    fetch(`${window.location.origin}/widget_data.json?widget_id=${widgetId}`).then((res) => {
      return res.json();
    }).then((w) => {
      const widget = w;
      if (widget.visualization.width !== undefined) delete widget.visualization.width;
      if (widget.visualization.height !== undefined) delete widget.visualization.height;

      this.setState({
        loading: false,
        widget
      });
    });
  }

  render() {
    if (this.state.loading) { return null; }
    return (
      <VegaChart
        data={this.state.widget.visualization}
        theme={getVegaTheme()}
        reloadOnResize
      />
    );
  }
}

WidgetBlock.propTypes = {
  item: PropTypes.object.isRequired
};

export default WidgetBlock;

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

import { VegaChart } from 'widget-editor';

// /widget_data.json?widget_id=
class WidgetBlock extends React.Component {
  constructor(props) {
    super(props);
    this.widgetConfig = null;
    this.state = {
      loading: true,
      widget: null
    };
  }

  componentWillMount() {
    const { item } = this.props;
    this.getChart(item.content.widgetId);
  }

  shouldComponentUpdate(nextProps, nextState) {
    // This fixes a bug where the widgets would reload when hovering them
    return this.state !== nextState
      || this.props.item.content.widgetId !== nextProps.item.content.widgetId;
  }

  getChart(widgetId) {
    fetch(`${window.location.origin}/widget_data.json?widget_id=${widgetId}`).then((res) => {
      return res.json();
    }).then((w) => {
      const widget = w;
      if (widget.visualization.width !== undefined) delete widget.visualization.width;
      if (widget.visualization.height !== undefined) delete widget.visualization.height;

      this.setState({
        loading: false,
        widget
      });
    });
  }

  render() {
    if (this.state.loading) { return null; }
    return (
      <VegaChart
        data={this.state.widget.visualization}
        reloadOnResize
      />
    );
  }
}

WidgetBlock.propTypes = {
  item: PropTypes.object.isRequired
};

export default WidgetBlock;

Fix a bug where widgets wouldn't be rendered w/ their themes in wysiwyg


// Login Controller
// ================
// Handles routing for logging into the pp

'use strict';

let express           = require('express'),
    QuotesController  = express.Router(),
    QClient           = require('q-client'),
    quotes            = new QClient(process.env.QClientID, process.env.QClientSecret);

    console.log(process.env.QClientID, process.env.QClientSecret);

QuotesController.route('/?')
  // GET /quotes/
  // ------------
  // Render quoting page
  .get(function(req, res, next) {
    res.render('quotes/index', {});
  })
  // POST /quotes/
  // ------------
  // Gets quotes
  .post(function(req, res, next) {
    quotes.getSubsidy('none', req.body.zip_code, req.body, function(err, results) {
      if (err) return next(new Error(err));
      res.json(JSON.parse(results));
    });
  });

module.exports = QuotesController;

// Login Controller
// ================
// Handles routing for logging into the pp

'use strict';

let express           = require('express'),
    QuotesController  = express.Router(),
    QClient           = require('q-client'),
    quotes            = new QClient(process.env.QClientID, process.env.QClientSecret);

    console.log(process.env.QClientID, process.env.QClientSecret);

QuotesController.route('/?')
  // GET /quotes/
  // ------------
  // Render quoting page
  .get(function(req, res, next) {
    res.render('quotes/index', {});
  })
  // POST /quotes/
  // ------------
  // Gets quotes
  .post(function(req, res, next) {
    quotes.getSubsidy('none', req.body.zip_code, req.body, function(err, results) {
      if (err)
        res.json({status: 'error', message: 'There was an error fetching quotes', details: err});
      else
        res.json(JSON.parse(results));
    });
  });

module.exports = QuotesController;

Fix whitespace between conditional statement.


suite('rb/resources/models/ValidateDiffModel', function() {
    var model;

    beforeEach(function() {
        model = new RB.ValidateDiffModel();
    });

    describe('methods', function() {
        describe('url', function() {
            it('Without local site', function() {
                expect(_.result(model, 'url')).toBe('/api/validation/diffs/');
            });

            it('With local site', function() {
                model.set('localSitePrefix', 's/test-site/');
                expect(_.result(model, 'url')).toBe('/s/test-site/api/validation/diffs/');
            });
        });
    });

    describe('toJSON', function() {
        it('repository field', function() {
            var data;

            model.set('repository', 123);
            data = model.toJSON();
            expect(model.get('repository')).toBe(123);
        });
    });
});

suite('rb/resources/models/ValidateDiffModel', function() {
    var model;

    beforeEach(function() {
        model = new RB.ValidateDiffModel();
    });

    describe('methods', function() {
        describe('url', function() {
            it('Without local site', function() {
                expect(_.result(model, 'url')).toBe('/api/validation/diffs/');
            });

            it('With local site', function() {
                model.set('localSitePrefix', 's/test-site/');
                expect(_.result(model, 'url')).toBe('/s/test-site/api/validation/diffs/');
            });
        });
    });

    describe('toJSON', function() {
        it('repository field', function() {
            var data;

            model.set('repository', 123);
            data = model.toJSON();
            expect(data.repository).toBe(123);
        });
    });
});

Fix up a broken js-test.


const fs = require("fs");
const path = require("path");

const gitDir = path.resolve(__dirname, "..", ".git");

function getPackageVersion() {
  const packageJson = require("../package.json");
  return packageJson.version;
}

function getHeadCommitHash() {
  try {
    fs.accessSync(gitDir);
  } catch (e) {
    return null;
  }

  // Find HEAD ref and read the commit hash from that ref
  const headRefInfo = fs.readFileSync(path.resolve(gitDir, "HEAD"), { encoding: "utf8" });
  if (headRefInfo.startsWith("ref:")) {
    const refPath = headRefInfo.slice(5).trim(); // ref: refs/heads/... to refs/heads/...
    return fs.readFileSync(path.resolve(gitDir, refPath), { encoding: "utf8" }).trim();
  } else {
    // Detached head, just the commit hash
    return headRefInfo.trim();
  }
}

function getPrettyVersion() {
  const packageVersion = getPackageVersion();
  const headCommitHash = getHeadCommitHash();

  return headCommitHash
    ? `v${packageVersion} (${headCommitHash.slice(0, 8)})`
    : packageVersion;
}

module.exports = {
  getPrettyVersion,
};

const fs = require("fs");
const path = require("path");

const gitDir = path.resolve(__dirname, "..", ".git");

function getPackageVersion() {
  const packageJson = require("../package.json");
  return packageJson.version;
}

function getHeadCommitHash() {
  try {
    fs.accessSync(gitDir);
  } catch (e) {
    return null;
  }

  // Find HEAD ref and read the commit hash from that ref
  const headRefInfo = fs.readFileSync(path.resolve(gitDir, "HEAD"), { encoding: "utf8" });
  if (headRefInfo.startsWith("ref:")) {
    const refPath = headRefInfo.slice(5).trim(); // ref: refs/heads/... to refs/heads/...
    return fs.readFileSync(path.resolve(gitDir, refPath), { encoding: "utf8" }).trim();
  } else {
    // Detached head, just the commit hash
    return headRefInfo.trim();
  }
}

function getPrettyVersion() {
  const packageVersion = getPackageVersion();
  const headCommitHash = getHeadCommitHash();

  return headCommitHash
    ? `v${packageVersion} (${headCommitHash.slice(0, 7)})`
    : packageVersion;
}

module.exports = {
  getPrettyVersion,
};

Use 7 chars for git commit hash, not 8


(function(){
'use strict';
angular.module('uk.ac.soton.ecs.videogular.plugins.cuepoints', [])
	.directive(
		'vgCuepoints',
		[function() {
			return {
				restrict: 'E',
				require: '^videogular',
				templateUrl: 'bower_components/videogular-cuepoints/cuepoints.html',
				scope: {
					cuepoints: '=vgCuepointsConfig',
					theme: '=vgCuepointsTheme',
				},
				link: function($scope, elem, attr, API) {
					// shamelessly stolen from part of videogular's updateTheme function
					function updateTheme(value) {
						if (value) {
							var headElem = angular.element(document).find("head");
							headElem.append("<link rel='stylesheet' href='" + value + "'>");
						}
					}

					$scope.calcLeft = function(cuepoint) {
						if (API.totalTime === 0) return '-1000';

						var videoLength = API.totalTime.getTime() / 1000;
						return (cuepoint.time * 100 / videoLength).toString();
					};

					updateTheme($scope.theme);
				},
			};
		}]);
})();

(function(){
'use strict';
angular.module('uk.ac.soton.ecs.videogular.plugins.cuepoints', [])
	.directive(
		'vgCuepoints',
		[function() {
			return {
				restrict: 'E',
				require: '^videogular',
				templateUrl: 'bower_components/videogular-cuepoints/cuepoints.html',
				scope: {
					cuepoints: '=vgCuepointsConfig',
					theme: '=vgCuepointsTheme',
				},
				link: function($scope, elem, attr, API) {
					// shamelessly stolen from part of videogular's updateTheme function
					function updateTheme(value) {
						if (value) {
							var headElem = angular.element(document).find("head");
							headElem.append("<link rel='stylesheet' href='" + value + "'>");
						}
					}

					$scope.calcLeft = function(cuepoint) {
						if (API.totalTime === 0) return '-1000';

						var videoLength = API.totalTime / 1000;
						return (cuepoint.time * 100 / videoLength).toString();
					};

					updateTheme($scope.theme);
				},
			};
		}]);
})();

Fix videoLength issue (API.totalTime.getTime() -> API.totalTime)


Accounts.onCreateUser(function(options, user) {
  if (options.profile) {
    options.profile.picture = 'http://graph.facebook.com/' + user.services.facebook.id + '/picture/?type=large';
    user.profile = options.profile;
  }
  return user;
});

Accounts.onCreateUser(function(options, user) {
  if(options.profile) {
    options.profile.picture = 'http://graph.facebook.com/' + user.services.facebook.id + '/picture/?type=large';
    user.profile = options.profile;
    user.username = user.profile.name;
  }
  return user;
});

Save name as user profile.


;(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(function () {
      return (root.Opbeat = factory())
    })
  } else {
    root.Opbeat = factory()
  }
}(this, function () {
  var defaultOptions = {
    apiHost: 'https://opbeat.com',
    logger: 'javascript',
    collectHttp: true,
    ignoreErrors: [],
    ignoreUrls: [],
    whitelistUrls: [],
    includePaths: [],
    collectWindowErrors: true,
    extra: {
      frame_info: {}
    }
  }

  function Opbeat () {
    this.options = defaultOptions
  }

  return {}

}))

;(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(function () {
      return (root.Opbeat = factory())
    })
  } else {
    root.Opbeat = factory()
  }
}(this, function () {
  var defaultOptions = {
    apiHost: 'https://opbeat.com',
    logger: 'javascript',
    collectHttp: true,
    ignoreErrors: [],
    ignoreUrls: [],
    whitelistUrls: [],
    includePaths: [],
    collectWindowErrors: true,
    extra: {
      frame_info: {}
    }
  }

  function Opbeat () {
    this.options = defaultOptions
  }

  /*
   * Configure Opbeat with Opbeat.com credentials and other options
   *
   * @param {object} options Optional set of of global options
   * @return {Opbeat}
   */

  Opbeat.prototype.config = function (options) {
    return this
  }

  /*
   * Installs a global window.onerror error handler
   * to capture and report uncaught exceptions.
   * At this point, install() is required to be called due
   * to the way TraceKit is set up.
   *
   * @return {Opbeat}
   */

  Opbeat.prototype.install = function () {
    return this

  }

  /*
   * Uninstalls the global error handler.
   *
   * @return {Opbeat}
   */
  Opbeat.prototype.uninstall = function () {
    return this
  }

  /*
   * Manually capture an exception and send it over to Opbeat.com
   *
   * @param {error} ex An exception to be logged
   * @param {object} options A specific set of options for this error [optional]
   * @return {Opbeat}
   */
  Opbeat.prototype.captureException = function (ex, options) {
    return this
  }

  /*
   * Set/clear a user to be sent along with the payload.
   *
   * @param {object} user An object representing user data [optional]
   * @return {Opbeat}
   */
  Opbeat.prototype.setUserContext = function (user) {
    return this
  }

  /*
   * Set extra attributes to be sent along with the payload.
   *
   * @param {object} extra An object representing extra data [optional]
   * @return {Opbeat}
   */
  Opbeat.prototype.setExtraContext = function (extra) {
    return this
  }

  return new Opbeat()

}))

Add public interface from previous client.


"use strict";

var commonBaseDir = "../../../";

var tst = require(commonBaseDir + "common/test/webdriverTestHelper");
var webpackDevServer = require(commonBaseDir + "common/test/webpackDevServerHelper");
var webpackConfig = require("../../webpack.config.js");

var expect = require("chai").expect;

webpackDevServer.start(webpackConfig);

tst.bootstrap({withConnectServer: false});

tst.describe("webpack basic example", function() {

    tst.describe("index page", function() {
        tst.before(function() {
            this.driver.get("http://localhost:5000");
        });

        tst.it("should have the right title", function() {
            this.driver.getTitle().then(function(title) {
                expect(title).to.equal("Webpack Examples | Basic With Require");
            });
        });

        tst.it("should have the right main heading", function() {
            expect("h1").dom.to.have.text("Hello webpack dev server!");
        });
    });

});

"use strict";

var commonBaseDir = "../../../";

var tst = require(commonBaseDir + "common/test/webdriverTestHelper");
var webpackDevServer = require(commonBaseDir + "common/test/webpackDevServerHelper");
var webpackConfig = require("../../webpack.config.js");

var expect = require("chai").expect;

webpackDevServer.start(webpackConfig);

tst.bootstrap({withConnectServer: false});

tst.describe("webpack basic example", function() {

    tst.describe("index page", function() {
        tst.before(function() {
            this.driver.get("http://localhost:5000");
        });

        tst.it("should have the right title", function() {
            this.driver.getTitle().then(function(title) {
                expect(title).to.equal("Webpack Examples | Basic With Require");
            });
        });

        tst.it("should have the right main heading", function() {
            expect("h1").dom.to.have.text("Hello webpack dev server!");
        });
    });

    tst.describe("/webpack-dev-server/", function() {
        tst.before(function() {
            this.driver.get("http://localhost:5000/webpack-dev-server/");
        });

        tst.it("should have the webpack ok status", function() {
            expect("#status").dom.to.have.text("App ready.");
        });

        tst.it("should have the right main heading", function() {
            this.driver.switchTo().frame("iframe");
            expect("h1").dom.to.have.text("Hello webpack dev server!");
            this.driver.switchTo().defaultContent();
        });
    });

});

Add tests to ensure the /webpack-dev-server/ endpoint is available


import forEach from 'lodash/collection/forEach';
import isEmpty from 'lodash/lang/isEmpty';
import isFunction from 'lodash/lang/isFunction';
import snakeCase from 'lodash/string/snakeCase';
import { Store, toImmutable } from 'nuclear-js';

export default function createStore(initialState, handlers) {
  const immutableState = toImmutable(initialState);

  const spec = {
    getInitialState() {
      return immutableState;
    },

    initialize() {
      if (!isEmpty(handlers)) {
        forEach(handlers, (handler, handlerName) => {
          const ACTION_NAME = snakeCase(handlerName || '').toUpperCase();

          if (!ACTION_NAME) {
            throw new Error('Frux#createStore: handler must be a named function.');
          }

          if (isFunction(handler)) {
            this.on(ACTION_NAME, (currentState, payload) => {
              return handler.call(null, currentState, payload, immutableState);
            });
          }
        });
      }
    }
  };

  return new Store(spec);
}

import forEach from 'lodash/collection/forEach';
import isEmpty from 'lodash/lang/isEmpty';
import isFunction from 'lodash/lang/isFunction';
import snakeCase from 'lodash/string/snakeCase';
import { Store, toImmutable } from 'nuclear-js';

export default function createStore(initialState, handlers) {
  const immutableState = toImmutable(initialState);

  const spec = {
    getInitialState() {
      return immutableState;
    },

    initialize() {
      if (!isEmpty(handlers)) {
        forEach(handlers, (handler, handlerName = '') => {
          const ACTION_NAME = snakeCase(handlerName).toUpperCase();

          if (!ACTION_NAME) {
            throw new Error('Frux#createStore: handler must be a named function.');
          }

          if (isFunction(handler)) {
            this.on(ACTION_NAME, (currentState, payload) => {
              return handler.call(null, currentState, payload, immutableState);
            });
          }
        });
      }
    }
  };

  return new Store(spec);
}

Replace line endings to lf and add default param as empty string for `handlerName`


import React from 'react'
import Radium from 'radium'
import Colors from './Colors'

let container = {
    height: '3rem',
    width: '4rem',
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center'
}

class Badge extends React.Component {
  render() {
    return (
      <section
        style={[
          container,
          this.props.background && { background: this.props.background }
        ]}>
        {this.props.children}
      </section>
    );
  }
}

export default Radium(Badge)

import React from 'react'
import Radium from 'radium'
import Colors from './Colors'

let container = {
    height: '3rem',
    minWidth: '4rem',
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center'
}

class Badge extends React.Component {
  render() {
    return (
      <section
        style={[
          container,
          this.props.background && { background: this.props.background }
        ]}>
        {this.props.children}
      </section>
    );
  }
}

export default Radium(Badge)

Use minWidth instead of width for badges.


/*
 * Copyright (c) 2017
 *
 * This file is licensed under the Affero General Public License version 3
 * or later.
 *
 * See the COPYING-README file.
 *
 */

(function() {
	if (!OCA.Weather) {
		/**
		 * Namespace for the files app
		 * @namespace OCA.Weather
		 */
		OCA.Weather = {};
	}

	/**
	 * @namespace OCA.Weather.Admin
	 */
	OCA.Weather.Admin = {
		initialize: function() {
			$('#submitOWMApiKey').on('click', _.bind(this._onClickSubmitOWMApiKey, this));
		},

		_onClickSubmitOWMApiKey: function () {
			OC.msg.startSaving('#OWMApiKeySettingsMsg');

			var request = $.ajax({
				url: OC.generateUrl('/apps/weather/settings/apikey'),
				type: 'POST',
				data: {
					apikey: $('#openweathermap-api-key').val()
				}
			});

			request.done(function (data) {
				$('#openweathermap-api-key').val(data.apikey);
				OC.msg.finishedSuccess('#OWMApiKeySettingsMsg', 'Saved');
			});

			request.fail(function () {
				OC.msg.finishedError('#OWMApiKeySettingsMsg', 'Error');
			});
		}
	}
})();

$(document).ready(function() {
	OCA.Weather.Admin.initialize();
});

/*
 * Copyright (c) 2017
 *
 * This file is licensed under the Affero General Public License version 3
 * or later.
 *
 * See the COPYING-README file.
 *
 */

(function() {
	if (!OCA.Weather) {
		/**
		 * Namespace for the files app
		 * @namespace OCA.Weather
		 */
		OCA.Weather = {};
	}

	/**
	 * @namespace OCA.Weather.Admin
	 */
	OCA.Weather.Admin = {
		initialize: function() {
			$('#submitOWMApiKey').on('click', _.bind(this._onClickSubmitOWMApiKey, this));
		},

		_onClickSubmitOWMApiKey: function () {
			OC.msg.startSaving('#OWMApiKeySettingsMsg');

			var request = $.ajax({
				url: OC.generateUrl('/apps/weather/settings/apikey'),
				type: 'POST',
				data: {
					apikey: $('#openweathermap-api-key').val()
				}
			});

			request.done(function (data) {
				$('#openweathermap-api-key').val(data.apikey);
				OC.msg.finishedSuccess('#OWMApiKeySettingsMsg', 'Saved');
			});

			request.fail(function () {
				OC.msg.finishedError('#OWMApiKeySettingsMsg', 'Error');
			});
		}
	}
})();

window.addEventListener('DOMContentLoaded', function () {
	OCA.Weather.Admin.initialize();
});

Fix document.ready function for future Nextcloud versions


$(function() {
  // Remove flash notifications from dom after 2.5 seconds
  if ($('#flash_notifications').length) {
    setTimeout(function() {
      $('#flash_notifications').animate({
        height: '0'
      }, 500, function() {
        $(this).remove();
      })
    }, 2500);
  }
});


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

var alertMessage = 'Sorry! We cannot geolocate you. Please enter a zipcode';

function getGeolocation() {
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(displayLocation, displayError);
  }
  else {
    console.log('Browser does not support geolocation');
    alert(alertMessage);
  }
}

function displayLocation(position) {
  var lat = position.coords.latitude;
  var lng = position.coords.longitude;
  var xhttp = new XMLHttpRequest();
  xhttp.open('POST', '/data?lat=' + lat + '&lng=' + lng, true);
  xhttp.send();
}

function displayError() {
  // TODO: Write error to user interface
  console.log('Geolocation not enabled');
  alert(alertMessage);
}

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

var alertMessage = 'Sorry! We cannot geolocate you. Please enter a zipcode';

function getGeolocation() {

  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(displayLocation, displayError);
  }
  else {
    console.log('Browser does not support geolocation');
    alert(alertMessage);
  }
}

function displayLocation(position) {
  var lat = position.coords.latitude;
  var lng = position.coords.longitude;
  var xhttp = new XMLHttpRequest();
  xhttp.open('POST', '/data?lat=' + lat + '&lng=' + lng, true);
  xhttp.send();
}

function displayError() {
  console.log('Geolocation not enabled');
  alert(alertMessage);
}

function getZip() {
  var zip = document.getElementById('zipCode').value;
  var xhttp = new XMLHttpRequest();
  xhttp.open('POST', '/data?zipCode=' + zip, true);
  xhttp.send();
}

Create post request for zipcode


import "../core/global";
import "../net/parseURLQueryTerms"

var sn_env = {
	debug : false,
	host : 'data.solarnetwork.net',
	tls : (function() {
		return (global !== undefined
			&& global.locaion !== undefined
			&& global.location.protocol !== undefined
			&& global.location.protocol.toLowerCase().indexOf('https') === 0 ? true : false);
	}()),
	path : '/solarquery',
	solarUserPath : '/solaruser',
	secureQuery : false
};

function sn_env_setDefaultEnv(defaults) {
	var prop;
	for ( prop in defaults ) {
		if ( defaults.hasOwnProperty(prop) ) {
			if ( sn_env[prop] === undefined ) {
				sn_env[prop] = defaults[prop];
			}
		}
	}
}

function sn_env_setEnv(environment) {
	var prop;
	for ( prop in environment ) {
		if ( environment.hasOwnProperty(prop) ) {
			sn_env[prop] = environment[prop];
		}
	}
}

sn.env = sn_env;
sn.setEnv = sn_env_setEnv;
sn.setDefaultEnv = sn_env_setDefaultEnv;

if ( global !== undefined
		&& global.location !== undefined
		&& global.location.search !== undefined ) {
	sn_env_setEnv(sn_net_parseURLQueryTerms(global.location.search));
}

import "../core/global";
import "../net/parseURLQueryTerms"

var sn_env = {
	debug : false,
	host : 'data.solarnetwork.net',
	tls : (function() {
		return (global !== undefined
			&& global.location !== undefined
			&& global.location.protocol !== undefined
			&& global.location.protocol.toLowerCase().indexOf('https') === 0 ? true : false);
	}()),
	path : '/solarquery',
	solarUserPath : '/solaruser',
	secureQuery : false
};

function sn_env_setDefaultEnv(defaults) {
	var prop;
	for ( prop in defaults ) {
		if ( defaults.hasOwnProperty(prop) ) {
			if ( sn_env[prop] === undefined ) {
				sn_env[prop] = defaults[prop];
			}
		}
	}
}

function sn_env_setEnv(environment) {
	var prop;
	for ( prop in environment ) {
		if ( environment.hasOwnProperty(prop) ) {
			sn_env[prop] = environment[prop];
		}
	}
}

sn.env = sn_env;
sn.setEnv = sn_env_setEnv;
sn.setDefaultEnv = sn_env_setDefaultEnv;

if ( global !== undefined
		&& global.location !== undefined
		&& global.location.search !== undefined ) {
	sn_env_setEnv(sn_net_parseURLQueryTerms(global.location.search));
}

Fix typo in variable name.


/**
 * Define an esi_link tag. This is a bit of a hack but it allows us to include a
 * link to a relative path (which jsdoc fails to recognize as a valid URL), and
 * it doesn't require modifying a template. Additionally, by adding directly to
 * the description, it works when docs are generated as markdown.
 *
 * This inserts a statement directing to the generated markdown documentation
 * produced by swagger-codegen.
 *
 * @private
 */
exports.defineTags = function(dictionary) {
  dictionary.defineTag('esi_link', {
    mustHaveValue: true,
    mustNotHaveDescription: true,
    onTagged: function(doclet, tag) {
      let value = tag.value;
      let period = value.indexOf('.');

      let url = 'https://github.com/lhkbob/eve-swagger-js/blob/master/generated/docs/';
      if (period >= 0) {
        // Class and method name provided
        let base = value.substring(0, period);
        let method = value.substring(period + 1);
        url = url + base + '.md#' + method;
      } else {
        // Assume it's just a class
        url = url + value + '.md';
      }

      let linkText = '\nThis promisifies a call to the generated [' + value
          + '](' + url + ').';
      doclet.description = doclet.description + linkText;
    }
  });
};

/**
 * Define an esi_link tag. This is a bit of a hack but it allows us to include a
 * link to a relative path (which jsdoc fails to recognize as a valid URL), and
 * it doesn't require modifying a template. Additionally, by adding directly to
 * the description, it works when docs are generated as markdown.
 *
 * This inserts a statement directing to the generated markdown documentation
 * produced by swagger-codegen.
 *
 * @private
 */
exports.defineTags = function(dictionary) {
  dictionary.defineTag('esi_link', {
    mustHaveValue: true,
    mustNotHaveDescription: true,
    onTagged: function(doclet, tag) {
      let value = tag.value;
      let period = value.indexOf('.');

      let linkText;
      let url = 'https://github.com/lhkbob/eve-swagger-js/blob/master/generated/docs/';
      if (period >= 0) {
        // Class and method name provided
        let base = value.substring(0, period);
        let method = value.substring(period + 1);
        url = url + base + '.md#' + method;
        linkText = '\nThis promisifies a call to the generated [' + value
            + '](' + url + ').';
      } else {
        // Assume it's just a class
        url = url + value + '.md';
        linkText = '\nThis wraps the generated [' + value + '](' + url + ').';
      }

      doclet.description = doclet.description + linkText;
    }
  });
};

Improve text generation between classes and methods.


import Ember from 'ember';
import layout from './template';

const {
  computed
} = Ember;

export default Ember.Component.extend({
  layout: layout,
  tagName: 'th',
  classNameBindings: ['sortType', 'isSortable:sortable', 'isActiveColumn:active'],
  isSortable: false,

  bindSort: function () {
    var sort = this.get('sort'),
        component = this;

    if (sort) {
      this.set('isSortable', true);
      this.$().bind('click', function () {
        component.toggleOrderBy();
      });
    }

    // Initial Sort
    if (this.get('sortType')) {
      this.send('sortBy');
    }
  }.on('didInsertElement'),

  unBindSort: function () {
    this.$().unbind('click');
  }.on('willDestroyElement'),

  // Computed Properties
  isActiveColumn: computed('parentView.activeColumn', {
    get: function () {
      return this.get('parentView.activeColumn') === this;
    }
  }),

  toggleOrderBy: function () {
    var sortType = this.get('sortType') || 'asc';
    this.set('sortType', (sortType === 'asc') ? 'desc' : 'asc');
    this.send('sortBy');
  },

  // Actions
  actions:{
    sortBy: function () {
      this.get('parentView').send('onSort', this);
    }
  }
});

import Ember from 'ember';
import layout from './template';

const {
  computed
} = Ember;

export default Ember.Component.extend({
  attributeBindings: ['rowspan', 'colspan'],
  layout: layout,
  tagName: 'th',
  classNameBindings: ['sortType', 'isSortable:sortable', 'isActiveColumn:active'],
  isSortable: false,

  bindSort: function () {
    var sort = this.get('sort'),
        component = this;

    if (sort) {
      this.set('isSortable', true);
      this.$().bind('click', function () {
        component.toggleOrderBy();
      });
    }

    // Initial Sort
    if (this.get('sortType')) {
      this.send('sortBy');
    }
  }.on('didInsertElement'),

  unBindSort: function () {
    this.$().unbind('click');
  }.on('willDestroyElement'),

  // Computed Properties
  isActiveColumn: computed('parentView.activeColumn', {
    get: function () {
      return this.get('parentView.activeColumn') === this;
    }
  }),

  toggleOrderBy: function () {
    var sortType = this.get('sortType') || 'asc';
    this.set('sortType', (sortType === 'asc') ? 'desc' : 'asc');
    this.send('sortBy');
  },

  // Actions
  actions:{
    sortBy: function () {
      this.get('parentView').send('onSort', this);
    }
  }
});

Add rowspan and colspan attribute bindings


'use strict';

exports.isPast = function (month, year) {
  return Date.now() >= new Date(year, month);
};

exports.month = {
  parse: function (month) {
    return ~~month;
  },
  isValid: function (month) {
    if (typeof month !== 'number') return false;
    return month >= 1 && month <= 12;
  }
};

exports.year = {
  parse: function (year, pad) {
    year = ~~year;
    if (!pad) return year;
    var base = new Date().getFullYear().toString().substr(0, 2);
    var str = base + (year.toString().length === 2 ? year : '0' + year);
    return ~~str;
  },
  isValid: function (year) {
    if (typeof year !== 'number') return false;
    return year > 0;
  },
  isPast: function (year) {
    return new Date().getFullYear() > year;
  }
};

'use strict';

exports.isPast = function (month, year) {
  return Date.now() >= new Date(year, month);
};

exports.month = {
  parse: function (month) {
    return ~~month || void 0;
  },
  isValid: function (month) {
    if (typeof month !== 'number') return false;
    return month >= 1 && month <= 12;
  }
};

exports.year = {
  parse: function (year, pad) {
    year = ~~year;
    if (!pad) return year;
    var base = new Date().getFullYear().toString().substr(0, 2);
    var str = base + (year.toString().length === 2 ? year : '0' + year);
    return ~~str || void 0;
  },
  isValid: function (year) {
    if (typeof year !== 'number') return false;
    return year > 0;
  },
  isPast: function (year) {
    return new Date().getFullYear() > year;
  }
};

Return undefined when parsing would return 0


define([
    'marionette',
    'parse',
    //'localforage',
    'core/module',
    'intro/module',
    'app/module',
    'bitstamp/module'
  ], function (
    Marionette,
    Parse,
    //localforage,
    CoreModule,
    IntroModule,
    AppModule,
    BitstampModule
  ) {
  var app = new Marionette.Application();

  Parse.initialize(
    'TTkLNPMw8KOMzJUcjumW2aOb340MFbWZhfphVr0w',
    'l1AEye0vXbDUFQeoVnqT97hVGE08tzeaPqbvhzI5');

  /*localforage.config({
    name: 'tradeguard',
    version: 1.0,
    size: 4980736,
    storeName: 'keyvaluepairs'
  });*/

  app.module("Core", CoreModule);
  app.module("Intro", IntroModule);
  //app.module("App", AppModule);
  //app.module("Bitstamp", BitstampModule);

  return app;
});

define([
    'marionette',
    'parse',
    //'localforage',
    'core/module',
    'intro/module',
    'app/module',
    'bitstamp/module'
  ], function (
    Marionette,
    Parse,
    //localforage,
    CoreModule,
    IntroModule,
    AppModule,
    BitstampModule
  ) {
  var app = new Marionette.Application();

  Parse.initialize(
    'pRwIFCcvOTWPspF4RE3EAsZ5ZWiYZnsWKrI0Ov07',
    'tpoxsrjLcK2oeNwfNGM8kHI0fA7GXF45oFVBEKLH');

  /*localforage.config({
    name: 'tradeguard',
    version: 1.0,
    size: 4980736,
    storeName: 'keyvaluepairs'
  });*/

  app.module("Core", CoreModule);
  app.module("Intro", IntroModule);
  //app.module("App", AppModule);
  //app.module("Bitstamp", BitstampModule);

  return app;
});

Fix the Parse api keys


import { XMLTextElement } from 'substance'

export default class TableCellNode extends XMLTextElement {
  constructor (...args) {
    super(...args)

    this.rowIdx = -1
    this.colIdx = -1
  }

  get rowspan () {
    return _parseSpan(this.getAttribute('rowspan'))
  }

  get colspan () {
    return _parseSpan(this.getAttribute('colspan'))
  }

  isShadowed () {
    return this.shadowed
  }

  getMasterCell () {
    return this.masterCell
  }
}

TableCellNode.type = 'table-cell'

function _parseSpan (str) {
  let span = parseInt(str, 10)
  if (isFinite(span)) {
    return Math.max(span, 1)
  } else {
    return 1
  }
}

import { XMLTextElement } from 'substance'
import { TEXT } from '../kit'

export default class TableCellNode extends XMLTextElement {
  constructor (...args) {
    super(...args)

    this.rowIdx = -1
    this.colIdx = -1
  }

  get rowspan () {
    return _parseSpan(this.getAttribute('rowspan'))
  }

  get colspan () {
    return _parseSpan(this.getAttribute('colspan'))
  }

  isShadowed () {
    return this.shadowed
  }

  getMasterCell () {
    return this.masterCell
  }
}

TableCellNode.type = 'table-cell'

TableCellNode.schema = {
  content: TEXT('bold', 'italic', 'sup', 'sub', 'monospace', 'ext-link', 'xref', 'inline-formula')
}

function _parseSpan (str) {
  let span = parseInt(str, 10)
  if (isFinite(span)) {
    return Math.max(span, 1)
  } else {
    return 1
  }
}

Allow annotations and inline-nodes in TableCells.


class GelatoCollection extends Backbone.Collection {

  _handleRequestEvent(options) {
    let clonedOptions = _.clone(options);
    options.error = () => {
      this.state = 'standby';
      this._triggerState();
      clonedOptions.error && clonedOptions.error(...arguments);
    };
    options.success = () => {
      this.state = 'standby';
      this._triggerState();
      clonedOptions.success && clonedOptions.success(...arguments);
    };
  }

  _triggerState() {
    console.log('state:', this.state);
    this.trigger('state', this.state, this);
    this.trigger('state:' + this.state, this);
  }

  fetch(options) {
    options = options || {};
    this.state = 'fetching';
    this._triggerState();
    this._handleRequestEvent(options);
    return Backbone.Collection.prototype.fetch.call(this, options);
  }

}

GelatoCollection.prototype.state = 'standby';

Gelato = Gelato || {};

Gelato.Collection = GelatoCollection;

class GelatoCollection extends Backbone.Collection {

  _handleRequestEvent(options) {
    let clonedOptions = _.clone(options);
    options.error = () => {
      this.state = 'standby';
      this._triggerState();
      clonedOptions.error && clonedOptions.error(...arguments);
    };
    options.success = () => {
      this.state = 'standby';
      this._triggerState();
      clonedOptions.success && clonedOptions.success(...arguments);
    };
  }

  _triggerState() {
    this.trigger('state', this.state, this);
    this.trigger('state:' + this.state, this);
  }

  fetch(options) {
    options = options || {};
    this.state = 'fetching';
    this._triggerState();
    this._handleRequestEvent(options);
    return Backbone.Collection.prototype.fetch.call(this, options);
  }

}

GelatoCollection.prototype.state = 'standby';

Gelato = Gelato || {};

Gelato.Collection = GelatoCollection;

Remove console logging from collection trigger state


import Ember from 'ember';

export default Ember.Service.extend({
  device: null,
  server: null,
  service: null,
  characteristic: null,

  isAvailable() {
    return 'bluetooth' in navigator;
  },

  async connectDevice(options) {
    let device = await navigator.bluetooth.requestDevice(options);
    let server = await device.gatt.connect();

    console.log(`Connected to bluetooth device: ${device.name}`);

    this.set('device', device);
    this.set('server', server);
  },

  async readValue(serviceName, characteristicName) {
    let service = await this.get('server').getPrimaryService(serviceName);
    let characteristic = await service.getCharacteristic(characteristicName);
    let value = await characteristic.readValue();

    return value.getUint8(0);
  }
});

import Ember from 'ember';

export default Ember.Service.extend({
  device: null,
  server: null,
  service: null,
  characteristic: null,

  isAvailable() {
    return 'bluetooth' in navigator;
  },

  async connectDevice(options) {
    let device = await navigator.bluetooth.requestDevice(options);
    let server = await device.gatt.connect();

    console.log(`Connected to bluetooth device: ${device.name}`);

    this.set('device', device);
    this.set('server', server);

    return  Ember.RSVP.Promise.resolve(device.name);
  },

  async readValue(serviceName, characteristicName) {
    let service = await this.get('server').getPrimaryService(serviceName);
    let characteristic = await service.getCharacteristic(characteristicName);
    let value = await characteristic.readValue();

    return Ember.RSVP.Promise.resolve(value.getUint8(0));
  }
});

Return promise in service methods


//
// FourSquare
//
hello.init({
	foursquare : {
		name : 'FourSquare',
		// Alter the querystring
		querystring : function(qs){
			var token = qs.access_token;
			delete qs.access_token;
			qs.oauth_token = token;
			qs.v = 20121125;
		},
		uri : {
			auth : 'https://foursquare.com/oauth2/authenticate',
			base : 'https://api.foursquare.com/v2/',
			'me' : 'users/self'
		},
		wrap : {
			me : function(o){
				if(o.meta&&o.meta.code===400){
					o = {
						error : {
							code : "access_denied",
							message : o.meta.errorDetail
						}
					};
					return o;
				}
				o = o.response.user;
				if(o.id){
					o.thumbnail = o.photo.prefix + '100x100'+ o.photo.suffix;
					o.name = o.firstName + ' ' + o.lastName;
				}
				return o;
			},
			'default' : function(){

			}
		}
	}
});

//
// FourSquare
//
hello.init({
	foursquare : {
		name : 'FourSquare',
		// Alter the querystring
		querystring : function(qs){
			var token = qs.access_token;
			delete qs.access_token;
			qs.oauth_token = token;
			qs.v = 20121125;
		},
		uri : {
			auth : 'https://foursquare.com/oauth2/authenticate',
			base : 'https://api.foursquare.com/v2/',
			'me' : 'users/self'
		},
		wrap : {
			me : function(o){
				if(o.meta&&o.meta.code===400){
					o = {
						error : {
							code : "access_denied",
							message : o.meta.errorDetail
						}
					};
					return o;
				}
				if(o && o.response){
					o = o.response.user;
					if(o.id){
						o.thumbnail = o.photo.prefix + '100x100'+ o.photo.suffix;
						o.name = o.firstName + ' ' + o.lastName;
					}
				}
				return o;
			},
			'default' : function(){

			}
		}
	}
});

Fix random bug in Foursquare