describe("Species;", function() {
  var Human, bob;

  beforeEach(function() {
    Human = Species({
      bringToLife: function() {
        return function(name) {
          this.name = name;
          this.sayName = function() {
            return "My name is " +  this.name;
          }
        }
      }
    });
    bob = new Human("bob");
  });

  describe("an object instance", function() {

    it("has the attributes specified in the 'bringToLife' function", function(){
      expect(bob.name).toEqual("bob");
      expect(bob.sayName()).toEqual("My name is bob");
    });

    it("can override a specified attribute", function() {
      expect(bob.sayName()).toEqual("My name is bob");
      bob.sayName = function() { return "I'm not telling" };  // redefine the function
      expect(bob.sayName()).toEqual("I'm not telling");
    });

    it("has attributes that are completely separate from other object instances", function() {
      jane = new Human("jane");
      expect(jane.sayName()).toEqual("My name is jane");
      bob.sayName = function() { return "I'm not telling" };  // redefine bob's function
      expect(jane.sayName()).toEqual("My name is jane");  // jane's function is not affected
      bob.sayName = jane.sayName;  // restore the function to bob
      expect(bob.sayName()).toEqual("My name is bob");
    });

  });

});

describe("Species", function() {
  var Human, bob;

  beforeEach(function() {
    Human = Species({
      bringToLife: function() {
        this.wings = "Yes! now I can fly"; // no you can't. defining attributes here won't work.
        return function(name) {
          this.name = name;
          this.sayName = function() {
            return "My name is " +  this.name;
          }
        }
      }
    });
    bob = new Human("bob");
  });

  describe("object instance", function() {

    it("has the attributes specified in the 'bringToLife' function (in the inner function)", function(){
      expect(bob.name).toEqual("bob");
      expect(bob.sayName()).toEqual("My name is bob");
    });

    it("does NOT have the attributes specified outside the inner function in the 'bringToLife' function", function() {
      expect(bob.wings).toBe(undefined);
    });

    it("can override a specified attribute", function() {
      expect(bob.sayName()).toEqual("My name is bob");
      bob.sayName = function() { return "I'm not telling" };  // redefine the function
      expect(bob.sayName()).toEqual("I'm not telling");
    });

    it("has attributes that are completely separate from other object instances", function() {
      jane = new Human("jane");
      expect(jane.sayName()).toEqual("My name is jane");
      bob.sayName = function() { return "I'm not telling" };  // redefine bob's function
      expect(jane.sayName()).toEqual("My name is jane");  // jane's function is not affected
      bob.sayName = jane.sayName;  // restore the function to bob
      expect(bob.sayName()).toEqual("My name is bob");
    });

  });

});

Test incorrect creation of attributes.

Test incorrect creation of attributes.


// @flow
import React from "react";
import { connect } from "react-redux";
import Dialog from "material-ui/Dialog";
import FlatButton from "material-ui/FlatButton";
import { isDirty } from "../accessors";
import { logout } from "../actionCreators";

type Props = {
  isDirty: boolean,
  open: boolean,
  cancel: () => void,
  logout: () => void
};

const LogoutPrompt = (props: Props) =>
  <Dialog
    open={props.open}
    actions={[
      <FlatButton label="Cancel" primary={true} onTouchTap={props.cancel} />,
      <FlatButton
        label={props.isDirty ? "Discard" : "Logout"}
        secondary={true}
        onTouchTap={() => {
          props.logout();
          // Ideally logging out would clear the drawer's "attempting to log out" state, but for now we cheat.
          props.cancel();
        }}
      />
    ]}
  >

      Your Jounal is not fully saved. Are you sure you want to log out?
      {props.isDirty ||

          Update: Your journal has now been saved! It is safe to logout.

}

  </Dialog>;

const mapStateToProps = state => ({
  isDirty: isDirty(state)
});

const mapDispatchToProps = { logout };

export default connect(mapStateToProps, mapDispatchToProps)(LogoutPrompt);

// @flow
import React from "react";
import { connect } from "react-redux";
import Dialog from "material-ui/Dialog";
import FlatButton from "material-ui/FlatButton";
import { isDirty } from "../accessors";
import { logout } from "../actionCreators";

type Props = {
  isDirty: boolean,
  open: boolean,
  cancel: () => void,
  logout: () => void
};

const WARNING = `Your Jounal is not fully saved. Are you sure you want to log out?`;

const LogoutPrompt = (props: Props) =>
  <Dialog
    open={props.open}
    actions={[
      <FlatButton label="Cancel" primary={true} onTouchTap={props.cancel} />,
      <FlatButton
        label={props.isDirty ? "Discard" : "Logout"}
        secondary={true}
        onTouchTap={() => {
          props.logout();
          // Ideally logging out would clear the drawer's "attempting to log out" state, but for now we cheat.
          props.cancel();
        }}
      />
    ]}
  >

      {props.isDirty
        ? WARNING
        : <s>
            {WARNING}
          </s>}
      {props.isDirty ||

          Update: Your journal has now been saved. It is safe
          to logout.

}

  </Dialog>;

const mapStateToProps = state => ({
  isDirty: isDirty(state)
});

const mapDispatchToProps = { logout };

export default connect(mapStateToProps, mapDispatchToProps)(LogoutPrompt);

Improve message for recovered dirty state.


import React from 'react';
import './RecentTracks.css';
import RecentTrack from './RecentTrack.js';
import _map from 'lodash/map';
import {
  fetchRecentTracks
} from '../actions/action-creators';

let RecentTracks = React.createClass ({

  componentWillMount() {
    fetchRecentTracks();
  },

  render() {

    const {
      recentTracks
    } = this.props;

    let RecentTrackElement;
    if (recentTracks) {
      RecentTrackElement = _map(recentTracks, (recentTrack, i) => (
        <RecentTrack
          key={i}
          recentTrack={recentTrack} />
      ));
    }

    return (
    <section
      className="RecentTracks">
      {RecentTrackElement}
    </section>
    );
  }
});

export default RecentTracks;

import React from 'react';
import './RecentTracks.css';
import RecentTrack from './RecentTrack.js';
import _map from 'lodash/map';
import _isEqual from 'lodash/isEqual';
import {
  fetchRecentTracks
} from '../actions/action-creators';

let RecentTracks = React.createClass ({

  shouldComponentUpdate(nextProps){
    return !_isEqual(nextProps.recentTracks,this.props.recentTracks);
  },

  componentWillMount() {
    fetchRecentTracks();
    // this.updateInterval = setInterval(() => fetchRecentTracks(2), 3000);
  },

  render() {

    const {
      recentTracks
    } = this.props;

    let RecentTrackElement;
    if (recentTracks) {
      RecentTrackElement = _map(recentTracks, (recentTrack, i) => (
        <RecentTrack
          key={i}
          recentTrack={recentTrack} />
      ));
    }

    return (
    <section
      className="RecentTracks">
      {RecentTrackElement}
    </section>
    );
  }
});

export default RecentTracks;

Check if a track is playing

Check if a track is playing


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

const SentenceList = ({sentences, isLoading}) => (

    {isLoading ? (

    ) : (

            Sentence
            CreatedAt
            Source

          {
            sentences.map((sentence, index) => (

                {sentence.sentence}
                {sentence.createdAt.toLocaleString()}
                {sentence.source.title}

            ))
          }

    )}

)

SentenceList.propTypes = {
  sentences: PropTypes.array.isRequired,
  isLoading: PropTypes.bool.isRequired,
}

export default SentenceList

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

const SentenceList = ({sentences, isLoading}) => (

    {isLoading ? (

    ) : (

            Sentence
            Detail
            CreatedAt
            Source

          {
            sentences.map((sentence, index) => (

                {sentence.sentence}
                {sentence.detail}
                {sentence.createdAt.toLocaleString()}
                {sentence.source.title}

            ))
          }

    )}

)

SentenceList.propTypes = {
  sentences: PropTypes.array.isRequired,
  isLoading: PropTypes.bool.isRequired,
}

export default SentenceList

Edit sentence list to show a detail

Edit sentence list to show a detail


/**
 * React Starter Kit (https://www.reactstarterkit.com/)
 *
 * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

import Promise from 'bluebird';
import fetch, { Request, Headers, Response } from 'node-fetch';
import { host } from '../../config';

fetch.Promise = Promise;

function localUrl(url) {
  if (url.startsWith('//')) {
    return `https:${url}`;
  }

  if (url.startsWith('http')) {
    return url;
  }

  return `http://${host}${url}`;
}

function localFetch(url, options) {
  return fetch(localUrl(url), options);
}

export { localFetch as default, Request, Headers, Response };

/**
 * React Starter Kit (https://www.reactstarterkit.com/)
 *
 * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

import Promise from 'bluebird';
import fetch, { Request, Headers, Response } from 'node-fetch';
import { host } from '../../config';

fetch.Promise = Promise;
Response.Promise = Promise;

function localUrl(url) {
  if (url.startsWith('//')) {
    return `https:${url}`;
  }

  if (url.startsWith('http')) {
    return url;
  }

  return `http://${host}${url}`;
}

function localFetch(url, options) {
  return fetch(localUrl(url), options);
}

export { localFetch as default, Request, Headers, Response };

Use bluebird Promise library in node-fetch > Response


import React, { PureComponent } from 'react';
import Box from '../box';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { COLORS, TINTS } from '../../constants';
import theme from './theme.css';

const factory = (baseType, type, defaultElement) => {
  class Text extends PureComponent {
    render() {
      const { children, className, color, element, tint, ...others } = this.props;

      const classNames = cx(theme[baseType], theme[type], theme[color], theme[tint], className);

      const Element = element || defaultElement;

      return (
        <Box className={classNames} data-teamleader-ui={baseType} element={Element} {...others}>
          {children}
        </Box>
      );
    }
  }

  Text.propTypes = {
    children: PropTypes.node,
    className: PropTypes.string,
    color: PropTypes.oneOf(COLORS),
    element: PropTypes.node,
    tint: PropTypes.oneOf(TINTS),
  };

  Text.defaultProps = {
    element: null,
    tint: 'darkest',
  };

  return Text;
};

export { factory as textFactory };

import React, { PureComponent } from 'react';
import Box from '../box';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { COLORS, TINTS } from '../../constants';
import theme from './theme.css';

const factory = (baseType, type, defaultElement) => {
  class Text extends PureComponent {
    render() {
      const { children, className, color, element, ellipsis, tint, ...others } = this.props;

      const classNames = cx(
        theme[baseType],
        theme[type],
        theme[color],
        theme[tint],
        {
          [theme['overflow-ellipsis']]: ellipsis,
        },
        className,
      );

      const Element = element || defaultElement;

      return (
        <Box className={classNames} data-teamleader-ui={baseType} element={Element} {...others}>
          {children}
        </Box>
      );
    }
  }

  Text.propTypes = {
    children: PropTypes.node,
    className: PropTypes.string,
    color: PropTypes.oneOf(COLORS),
    element: PropTypes.node,
    ellipsis: PropTypes.bool,
    tint: PropTypes.oneOf(TINTS),
  };

  Text.defaultProps = {
    element: null,
    ellipsis: false,
    tint: 'darkest',
  };

  return Text;
};

export { factory as textFactory };

Add `ellipsis` prop & conditionally apply the `overflow-ellipsis` class from `ui-typography`


module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    jshint: {
      all: [
        'Gruntfile.js',
        'public/js/**/*.js',
        'test/client/**/*.js',
        'lib/**/*.js',
        'app.js'
      ],
      options: {
        expr: true,
        browser: true,
        node: true
      }
    },

    watch: {
      scripts: {
        files: ['public/js/**/*.js', 'test/client/**/*.js'],
        tasks: ['jshint']
      },
      karma: {
        files: ['public/js/**/*.js', 'test/client/**/*.js'],
        tasks: ['karma:unit:run']
      }
    },

    karma: {
      unit: {
        configFile: 'config/karma.conf.js',
        background: true
      },
      continuous: {
        singleRun: true,
        browsers: ['Firefox']
      },
      dev: {
        reporters: 'dots'
      }
    }

  });

  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-karma');

  grunt.registerTask('test', ['karma:continuous']);
};

module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    jshint: {
      all: [
        'Gruntfile.js',
        'public/js/**/*.js',
        'test/client/**/*.js',
        'lib/**/*.js',
        'app.js'
      ],
      options: {
        expr: true,
        browser: true,
        node: true
      }
    },

    watch: {
      scripts: {
        files: ['public/js/**/*.js', 'test/client/**/*.js'],
        tasks: ['jshint']
      },
      karma: {
        files: ['public/js/**/*.js', 'test/client/**/*.js'],
        tasks: ['karma:unit:run']
      }
    },

    karma: {
      unit: {
        configFile: 'config/karma.conf.js',
        background: true
      },
      single: {
        configFile: 'config/karma.conf.js',
        singleRun: true
      },
      dev: {
        reporters: 'dots'
      }
    }

  });

  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-karma');

  grunt.registerTask('test', ['karma:single']);
};

Remove continuous test grunt task in favor of single-run test


module.exports = function(grunt) {

  // 1. All configuration goes here
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    sass: {
        options: {
            sourceMap: true
        },
        dist: {
            files: {
                'public/css/calendar.css': 'public/sass/calendar.scss'
            }
        }
    },

    concat: {
      // 2. Configuration for concatinating files goes here.
      dist: {
        src: [
          'public/js/jquery-1.11.1.min.js',
          'public/js/moment.js',
          'public/js/bootstrap.min.js',
          'public/js/jquery.datetimepicker.js'
        ],
        dest: 'public/js/build/production.js'
      }
    },

    uglify: {
      build: {
        src: 'public/js/build/production.js',
        dest: 'public/js/build/production.min.js'
      }
    }

  });

  // 3. Where we tell Grunt we plan to use this plug-in.
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-sass');

  // 4. Where we tell Grunt what to do when we type "grunt" into the terminal.
  grunt.registerTask('default', ['concat', 'uglify', 'sass']);

};

module.exports = function(grunt) {

  // 1. All configuration goes here
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    sass: {
        options: {
            sourceMap: true
        },
        dist: {
            files: {
                'public/css/calendar.css': 'public/sass/calendar.scss'
            }
        }
    },

    concat: {
      // 2. Configuration for concatinating files goes here.
      dist: {
        src: [
          'bower_components/jquery/dist/jquery.min.js',
          'bower_components/moment/min/moment.min.js',
          'bower_components/bootstrap-sass/assets/javascripts/bootstrap.min.js',
          'bower_components/datetimepicker/jquery.datetimepicker.js'
        ],
        dest: 'public/js/build/production.js'
      }
    },

    uglify: {
      build: {
        src: 'public/js/build/production.js',
        dest: 'public/js/build/production.min.js'
      }
    }

  });

  // 3. Where we tell Grunt we plan to use this plug-in.
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-sass');

  // 4. Where we tell Grunt what to do when we type "grunt" into the terminal.
  grunt.registerTask('default', ['concat', 'uglify', 'sass']);

};

Update grunt to use bower_components

Update grunt to use bower_components


module.exports = function(grunt){
    grunt.loadNpmTasks('grunt-contrib-coffee');
    grunt.loadNpmTasks('grunt-contrib-watch');

    grunt.initConfig({
        coffee:{
            compileJoined: {
                options: {
                    join: true
                },
                files: {
                    'js/main.js': [
                        'coffee/*'
                    ]
                }
            },
        },
        watch: {
            coffee: {
                files: ['coffee/*.coffee', 'example/coffee/*.coffee'],
                tasks: 'coffee'
            }
        },
        bower: {
            target: {
                rjsConfig: 'app/config.js'
            }
        }
    });

    grunt.loadNpmTasks('grunt-bower-requirejs');

    grunt.registerTask('default', [
        'coffee', 'bower'
    ]);
};

module.exports = function(grunt){
    grunt.loadNpmTasks('grunt-contrib-coffee');
    grunt.loadNpmTasks('grunt-contrib-watch');

    grunt.initConfig({
        coffee:{
            compileJoined: {
                options: {
                    join: true
                },
                files: {
                    'js/main.js': [
                        'coffee/*'
                    ]
                }
            },
        },
        watch: {
            coffee: {
                files: ['coffee/*.coffee', 'example/coffee/*.coffee'],
                tasks: 'coffee'
            }
        },
        bower: {
            target: {
                rjsConfig: 'app/config.js'
            }
        }
    });

    grunt.loadNpmTasks('grunt-bower-requirejs');

    grunt.registerTask('default', [
        'coffee', 'bower'
    ]);

    grunt.registerTask('develop', [
        'default', 'watch'
    ]);
};

Break up 'default' task into 'default' and 'watch', so that traditional build processes can work


import { ReactiveVar } from 'meteor/reactive-var'
import { Template } from 'meteor/templating';

Template.dashboard.onCreated(function () {
  // Get reference to template instance
  const templateInstance = this;

  // Create reactive variable for Elasticsearch host
  templateInstance.elasticsearchHost = new ReactiveVar();

  // Handle changes to Elasticsearch host
  templateInstance.autorun(function () {
    // Get value of Elasticsearch host
    const host = templateInstance.elasticsearchHost.get();

    if (host) {
      Meteor.call('getElasticsearchData', host);
    }
  });
});

Template.dashboard.events({
  'submit #elasticsearch-host' (event, templateInstance) {
    // prevent default form action
    event.preventDefault();

    // Get Elasticsearch host from form
    const host = event.target.host.value;

    // Update Elasticsearch host reactive variable
    templateInstance.elasticsearchHost.set(host);
  }
});

import { ReactiveVar } from 'meteor/reactive-var'
import { Template } from 'meteor/templating';

Template.dashboard.onCreated(function () {
  // Get reference to template instance
  const templateInstance = this;

  // Create reactive variable for Elasticsearch host
  templateInstance.elasticsearchHost = new ReactiveVar();

  // Create reactive variable for Elasticsearch data
  templateInstance.elasticsearchData = new ReactiveVar();

  // Handle changes to Elasticsearch host
  templateInstance.autorun(function () {
    // Get value of Elasticsearch host
    const host = templateInstance.elasticsearchHost.get();

    if (host) {
      Meteor.call('getElasticsearchData', host, function (error, result) {
        if (error) {
          throw Meteor.Error('error', error)
        } else {
          // Update Elasticsearch data reactive variable
          templateInstance.elasticsearchData.set(result);
        }
      });
    }
  });
});

Template.dashboard.events({
  'submit #elasticsearch-host' (event, templateInstance) {
    // prevent default form action
    event.preventDefault();

    // Get Elasticsearch host from form
    const host = event.target.host.value;

    // Update Elasticsearch host reactive variable
    templateInstance.elasticsearchHost.set(host);
  }
});

Use getElasticsearchData method / reactive variable


var _window = (typeof window !== 'undefined' && window) || null;

export function open(uri, name) {
  var df = this.context.dataflow;
  if (_window && _window.open) {
    df.loader().sanitize(uri, {context:'open', name:name})
      .then(function(url) { _window.open(url, name); })
      .catch(function(e) { df.warn('Open url failed: ' + e); });
  } else {
    df.warn('Open function can only be invoked in a browser.');
  }
}

export function screen() {
  return _window ? _window.screen : {};
}

export function windowsize() {
  return _window
    ? [_window.innerWidth, _window.innerHeight]
    : [undefined, undefined];
}

var _window = (typeof window !== 'undefined' && window) || null;

export function open(uri, name) {
  var df = this.context.dataflow;
  if (_window && _window.open) {
    df.loader().sanitize(uri, {context:'open', name:name})
      .then(function(opt) { _window.open(opt.href, name); })
      .catch(function(e) { df.warn('Open url failed: ' + e); });
  } else {
    df.warn('Open function can only be invoked in a browser.');
  }
}

export function screen() {
  return _window ? _window.screen : {};
}

export function windowsize() {
  return _window
    ? [_window.innerWidth, _window.innerHeight]
    : [undefined, undefined];
}

Modify `open`function to use sanitize hash


const dbConnection = require("./mongoConnection");

let getCollectionFn = (collection) => {
	let _col = undefined;

	return () => {
		if (!_col) {
			_col = dbConnection().then(db => {
				return db.collection(collection);
			});
		}

		return _col;
	};
};

module.exports = {
	students: getCollectionFn("students"),
	studentCourses: getCollectionFn("studentCourses"),
	teachers: getCollectionFn("teachers"),
	teacherCourses: getCollectionFn("teacherCourses"),
	classes: getCollectionFn("classes"),
	assignments: getCollectionFn("assignments")
};

const dbConnection = require("./mongoConnection");

let getCollectionFn = (collection) => {
	let _col = undefined;

	return () => {
		if (!_col) {
			_col = dbConnection().then(db => {
				return db.collection(collection);
			});
		}

		return _col;
	};
};

module.exports = {
	students: getCollectionFn("students"),
	teachers: getCollectionFn("teachers"),
	classes: getCollectionFn("courses"),
	assignments: getCollectionFn("assignments")
};

Remove unneeded collections that are actually subdocuments Change name of classes collection to courses for consistency


var keystone = require('keystone'),
	Types = keystone.Field.Types;

// Create model. Additional options allow menu name to be used what auto-generating URLs
var MediaEligibility = new keystone.List('Media Eligibility', {
	autokey: { path: 'key', from: 'MediaEligibility', unique: true },
	map: { name: 'MediaEligibility' }
});

// Create fields
MediaEligibility.add({
	MediaEligibility: { type: String, label: 'Media Eligibility', required: true, index: true, initial: true }
});

// Define default columns in the admin interface and register the model
MediaEligibility.defaultColumns = 'MediaEligibility';
MediaEligibility.register();

var keystone = require('keystone'),
	Types = keystone.Field.Types;

// Create model. Additional options allow menu name to be used what auto-generating URLs
var MediaEligibility = new keystone.List('Media Eligibility', {
	autokey: { path: 'key', from: 'mediaEligibility', unique: true },
	map: { name: 'mediaEligibility' }
});

// Create fields
MediaEligibility.add({
	mediaEligibility: { type: String, label: 'Media Eligibility', required: true, index: true, initial: true }
});

// Define default columns in the admin interface and register the model
MediaEligibility.defaultColumns = 'mediaEligibility';
MediaEligibility.register();

Add Reference list model for Media Eligibilities.


function update_audio() {
    var audioplayer = document.getElementById("audioplayer");
    var src = "http://telechargement.rfi.fr.edgesuite.net/rfi/francais/audio/journaux/r001/journal_francais_facile_20h00_-_20h10_tu_";
    var today = new Date();
    console.log(today.getUTCHours());
    if (today.getUTCHours() < 20 || (today.getUTCHours() == 20 && today.getUTCMinutes() < 20)) {
        today -= 3600 * 24 * 1000;
        today = new Date(today);
    }
    src += today.getFullYear();
    if (today.getMonth() < 9) {
        src += "0";
    }
    src += today.getMonth()+1;
    src += today.getDate();
    src += ".mp3";
    console.log(src);
    if (audioplayer.src != src) {
        audioplayer.src = src;
    }
}

window.addEventListener("focus", update_audio);
document.addEventListener("DOMContentLoaded", update_audio);

function update_audio() {
    var audioplayer = document.getElementById("audioplayer");
    var src = "http://telechargement.rfi.fr.edgesuite.net/rfi/francais/audio/journaux/r001/journal_francais_facile_20h00_-_20h10_tu_";
    var today = new Date();
    console.log(today.getUTCHours());
    if (today.getUTCHours() < 20 || (today.getUTCHours() == 20 && today.getUTCMinutes() < 20)) {
        today -= 3600 * 24 * 1000;
        today = new Date(today);
    }
    src += today.getFullYear();
    if (today.getMonth() < 9) {
        src += "0";
    }
    src += today.getMonth()+1;

    if (today.getDate() < 10) {
        src += "0";
    }
    src += today.getDate();

    src += ".mp3";
    console.log(src);
    if (audioplayer.src != src) {
        audioplayer.src = src;
    }
}

window.addEventListener("focus", update_audio);
document.addEventListener("DOMContentLoaded", update_audio);

Fix error for the first days of the month.


import DomMock from '../../helpers/dom-mock';
import jsdom from 'mocha-jsdom';
import expect from 'expect';
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import { Portal } from '../../../portal/src/containers/Portal';
import FBLogin from '../../../portal/src/components/FBLogin';
import Subject from '../../../portal/src/components/Popup/Subject';

DomMock('');

describe('[Portal] Testing Portal Component', () => {
    jsdom({ skipWindowCheck: true });

    const props = {
        loading: false,
        token: 'xxxx',
        account: {},
        subject: '',
        surveyID: '',
        editSubject: false,
        preview: '',
        previewID: '',
        routing: {},
        editSubjectActions: () => {},
        subjectActions: () => {},
        questionsActions: () => {},
        previewActions: () => {},
        accountActions: () => {}
    };

    it('Portal: FB Login', () => {
        const content = TestUtils.renderIntoDocument(<Portal {...props} />);
        const fb = TestUtils.scryRenderedComponentsWithType(content, FBLogin);
        expect(fb.length).toEqual(1);
    });

    it('Portal: edit subject popup', () => {
        const editProps = Object.assign({}, props,
            { editSubject: true });
        const content = TestUtils.renderIntoDocument(<Portal {...editProps} />);
        const subject = TestUtils.findRenderedComponentWithType(content, Subject);
        expect(subject).toExist();
    });
});

import DomMock from '../../helpers/dom-mock';
import jsdom from 'mocha-jsdom';
import expect from 'expect';
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import { Portal } from '../../../portal/src/containers/Portal';
import FBLogin from '../../../portal/src/components/FBLogin';
import Subject from '../../../portal/src/components/Popup/Subject';

DomMock('');

describe('[Portal] Testing Portal Component', () => {
    jsdom({ skipWindowCheck: true });

    const props = {
        loading: false,
        token: 'xxxx',
        account: {},
        subject: '',
        surveyID: '',
        editSubject: false,
        preview: '',
        previewID: '',
        webpage: 'index',
        routing: {},
        editSubjectActions: () => {},
        subjectActions: () => {},
        questionsActions: () => {},
        previewActions: () => {},
        accountActions: () => {}
    };

    it('Portal: FB Login', () => {
        const content = TestUtils.renderIntoDocument(<Portal {...props} />);
        const fb = TestUtils.scryRenderedComponentsWithType(content, FBLogin);
        expect(fb.length).toEqual(1);
    });

    it('Portal: edit subject popup', () => {
        const editProps = Object.assign({}, props,
            { editSubject: true });
        const content = TestUtils.renderIntoDocument(<Portal {...editProps} />);
        const subject = TestUtils.findRenderedComponentWithType(content, Subject);
        expect(subject).toExist();
    });
});

Adjust unit test for new props webpage

Adjust unit test for new props webpage


import isSameUTCWeek from '../../../../_lib/isSameUTCWeek/index.js'
var weekdays = [
  'svētdienā',
  'pirmdienā',
  'otrdienā',
  'trešdienā',
  'ceturtdienā',
  'piektdienā',
  'sestdienā'
]

var formatRelativeLocale = {
  lastWeek: function(date, baseDate, options) {
    if (isSameUTCWeek(date, baseDate, options)) {
      return "eeee 'plkst.' p"
    }

    var weekday = weekdays[date.getUTCDay()]
    return "'Pagājušā' " + weekday + " 'plkst.' p"
  },
  yesterday: "'Vakar plkst.' p",
  today: "'Šodien plkst.' p",
  tomorrow: "'Rīt plkst.' p",
  nextWeek: function(date, baseDate, options) {
    if (isSameUTCWeek(date, baseDate, options)) {
      return "eeee 'plkst.' p"
    }

    var weekday = weekdays[date.getUTCDay()]
    return "'Nākamajā' " + weekday + " 'plkst.' p"
  },
  other: 'P'
}

export default function formatRelative(token, date, baseDate, options) {
  var format = formatRelativeLocale[token]

  if (typeof format === 'function') {
    return format(date, baseDate, options)
  }

  return format
}

import isSameUTCWeek from '../../../../_lib/isSameUTCWeek/index.js'
var weekdays = [
  'svētdienā',
  'pirmdienā',
  'otrdienā',
  'trešdienā',
  'ceturtdienā',
  'piektdienā',
  'sestdienā'
]

var formatRelativeLocale = {
  lastWeek: function(date, baseDate, options) {
    if (isSameUTCWeek(date, baseDate, options)) {
      return "eeee 'plkst.' p"
    }

    var weekday = weekdays[date.getUTCDay()]
    return "'Pagājušā " + weekday + " plkst.' p"
  },
  yesterday: "'Vakar plkst.' p",
  today: "'Šodien plkst.' p",
  tomorrow: "'Rīt plkst.' p",
  nextWeek: function(date, baseDate, options) {
    if (isSameUTCWeek(date, baseDate, options)) {
      return "eeee 'plkst.' p"
    }

    var weekday = weekdays[date.getUTCDay()]
    return "'Nākamajā " + weekday + " plkst.' p"
  },
  other: 'P'
}

export default function formatRelative(token, date, baseDate, options) {
  var format = formatRelativeLocale[token]

  if (typeof format === 'function') {
    return format(date, baseDate, options)
  }

  return format
}

Fix formatRelative in Latvian locale

Fix formatRelative in Latvian locale


chrome.identity.getAuthToken({ 'interactive': true }, function(token) {
    console.log("token" + token);
});

var encoded = $("div.ep[data-eid]").attr("data-eid");
if (encoded !== undefined) {
    var eid = atob(encoded);
}

// chrome.identity.getAuthToken({ 'interactive': true }, function(token) {
//     console.log("token" + token);
// });

$(document).on('click', '.ep-gl-count', function () {
    var encoded = $("div.ep[data-eid]").attr("data-eid");
    if (encoded === undefined) {
        return;
    }

    var decoded = atob(encoded);
    console.log("Current event ID: " + decoded.split(" ")[0]);

    var costElem = $('#gcal-cost');

    if (!costElem.length) {
        $("<p id='gcal-cost'>Cost of the meeting is: </p>").insertAfter(".ep-gl-count");
    } else {
        costElem.html('Updated cost of the meeting is: ');
    }
});

Add cost element to the event detail

Add cost element to the event detail


'use strict';

var helpers = require('../helpers');

module.exports = {
  'name': 'no-duplicate-properties',
  'defaults': {
    'exclude': []
  },
  'detect': function (ast, parser) {
    var result = [];

    ast.traverseByType('block', function (block) {
      var properties = [],
          items = [],
          warnMessage = false;

      block.eachFor('declaration', function (declaration) {
        items.push(declaration);
      });

      items.reverse();

      items.forEach(function (declaration) {
        warnMessage = false;

        declaration.eachFor('property', function (item) {
          var property = item.content[0].content;

          if (properties.indexOf(property) !== -1 && properties.length >= 1) {
            if (parser.options.exclude.indexOf(property) !== -1 && properties[properties.length - 1] !== property) {
              warnMessage = 'Excluded duplicate properties must directly follow each other.';
            }
            else if (parser.options.exclude.indexOf(property) === -1) {
              warnMessage = 'Duplicate properties are not allowed within a block';
            }
          }

          properties.push(property);

          if (warnMessage) {
            result = helpers.addUnique(result, {
              'ruleId': parser.rule.name,
              'line': item.start.line,
              'column': item.start.column,
              'message': warnMessage,
              'severity': parser.severity
            });
          }
        });
      });
    });

    return result;
  }
};

'use strict';

var helpers = require('../helpers');
var selectorHelpers = require('../selector-helpers');

module.exports = {
  'name': 'no-duplicate-properties',
  'defaults': {
    'exclude': []
  },
  'detect': function (ast, parser) {
    var result = [];

    ast.traverseByType('block', function (block) {
      var properties = [],
          items = [],
          warnMessage = false;

      block.eachFor('declaration', function (declaration) {
        items.push(declaration);
      });

      items.reverse();

      items.forEach(function (declaration) {
        warnMessage = false;

        declaration.eachFor('property', function (item) {
          var property = '';
          item.content.forEach(function (subItem) {
            // Although not a selector the method here helps us construct the proper property name
            // taking into account any interpolation etc
            property += selectorHelpers.constructSelector(subItem);
          });
          if (properties.indexOf(property) !== -1 && properties.length >= 1) {
            if (parser.options.exclude.indexOf(property) !== -1 && properties[properties.length - 1] !== property) {
              warnMessage = 'Excluded duplicate properties must directly follow each other.';
            }
            else if (parser.options.exclude.indexOf(property) === -1) {
              warnMessage = 'Duplicate properties are not allowed within a block';
            }
          }

          properties.push(property);

          if (warnMessage) {
            result = helpers.addUnique(result, {
              'ruleId': parser.rule.name,
              'line': item.start.line,
              'column': item.start.column,
              'message': warnMessage,
              'severity': parser.severity
            });
          }
        });
      });
    });

    return result;
  }
};

Fix no duplicate property interp issue

:bug: Fix no duplicate property interp issue


import { createMuiTheme } from '@material-ui/core/styles'

// Theme
const primaryMainColor = '#9d4ba3'
const primaryContrastTextColor = '#fff'
const secondaryMainColor = '#4a90e2'
const secondaryContrastTextColor = '#fff'

const theme = createMuiTheme({
  palette: {
    primary: {
      // light: will be calculated from palette.primary.main,
      main: primaryMainColor,
      // dark: will be calculated from palette.primary.main,
      contrastText: primaryContrastTextColor
    },
    secondary: {
      // light: will be calculated from palette.primary.main,
      main: secondaryMainColor,
      // dark: will be calculated from palette.primary.main,
      contrastText: secondaryContrastTextColor
    }
  },
  typography: {
    fontSize: 14,
    fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif'
  },
  overrides: {
    MuiButtonBase: {
      root: {
        color: primaryMainColor
      }
    },
    MuiListSubheader: {
      root: {
        fontSize: 12,
        textTransform: 'uppercase'
      }
    }
  }
})

export default theme

import { createMuiTheme } from '@material-ui/core/styles'

// Theme
const primaryMainColor = '#9d4ba3'
const primaryContrastTextColor = '#fff'
const secondaryMainColor = '#4a90e2'
const secondaryContrastTextColor = '#fff'

const theme = createMuiTheme({
  palette: {
    primary: {
      // light: will be calculated from palette.primary.main,
      main: primaryMainColor,
      // dark: will be calculated from palette.primary.main,
      contrastText: primaryContrastTextColor
    },
    secondary: {
      // light: will be calculated from palette.primary.main,
      main: secondaryMainColor,
      // dark: will be calculated from palette.primary.main,
      contrastText: secondaryContrastTextColor
    }
  },
  typography: {
    fontSize: 14,
    fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif'
  },
  overrides: {
    MuiButtonBase: {
      root: {
        color: 'rgba(0, 0, 0, 0.87)'
      }
    },
    MuiListSubheader: {
      root: {
        fontSize: 12,
        textTransform: 'uppercase'
      }
    }
  }
})

export default theme

Change default button ripple color to almost-black


export default {
  // Entrypoint of the application. It is named main since that
  // is most common in other programming environments.
  entry: [
    // Entry point files.
    "./src/main.js",
    "./src/index.html"
  ],

  // Output of the built files to the specified path and filename.
  output: {
    path: __dirname + "/dist",
    filename: "bundle.js"
  },

  // Each file that is processed can be optionally handled by a 'loader'
  // which acts as a preprocessor.
  module: {
    loaders: [
      // The babel-loader handles all the transpilation work with
      // ES 2015 and JSX. See .babelrc for specifics about the configuration.
      {
        text: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader"
      },

      // The file loader allows for webpack to copy other static assets.
      {
        test: /\.html$/,
        loader: "file?name=[name].[ext]"
      }
    ]
  }
}

// Host and port
const HOST = process.env.HOST || '127.0.0.1'
const PORT = process.env.PORT || 3000

export default {
  // Entrypoint of the application. It is named main since that
  // is most common in other programming environments.
  entry: [
    // Entry point files.
    "./src/main.js",
    "./src/index.html"
  ],

  // Output of the built files to the specified path and filename.
  output: {
    path: __dirname + "/dist",
    filename: "bundle.js"
  },

  devServer: {
    host: HOST,
    port: PORT
  },

  // Each file that is processed can be optionally handled by a 'loader'
  // which acts as a preprocessor.
  module: {
    loaders: [
      // The babel-loader handles all the transpilation work with
      // ES 2015 and JSX. See .babelrc for specifics about the configuration.
      {
        text: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader"
      },

      // The file loader allows for webpack to copy other static assets.
      {
        test: /\.html$/,
        loader: "file?name=[name].[ext]"
      }
    ]
  }
}

Support configuring the host and port of devserver

The HOST and PORT env variables may now be used to explicitly set the
host and port.

Signed-off-by: Byron Ruth <e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98@devel.io>


(function () {

    app(function (api) {

        api.on("load:detail", function (path) {

            var result = path.split("/")[1] && api.searchResults[path.split("/")[1]],
                tmpl = $("#tmpl-detail").innerHTML.trim();

            $("#detail section").innerHTML = riot.render(tmpl, {
                name: result.fileinfo.name,
                path: result.fileinfo.path,
                size: result.file.size ? Math.round(result.file.size / 1000) + " KB" : "Unknown",
                modified: result.file.lastModifiedDate ? result.file.lastModifiedDate.toDateString() : "Unknown",
                icontype: api.iconclass(result.file.type),
                i: path.split("/")[1]
            });

            // Memory cleanup
            result = tmpl = null;

        });

    });

})();

(function () {

    app(function (api) {

        api.on("load:detail", function (path) {

            var result = path.split("/")[1] && api.searchResults[path.split("/")[1]],
                tmpl = $("#tmpl-detail").innerHTML.trim();

            $("#detail section").innerHTML = riot.render(tmpl, {
                name: result.fileinfo.name,
                path: result.fileinfo.path,
                size: result.file.size ? Math.round(result.file.size / 1000) + " KB" : "Unknown",
                modified: result.file.lastModifiedDate ? result.file.lastModifiedDate.toDateString() : "Unknown",
                icontype: api.iconclass(result.file.type),
                i: path.split("/")[1]
            });

            // Preview button handler
            $("#previewbtn").addEventListener("click", function (e) {
                e.preventDefault();

                var activityname = "open",
                    result = api.searchResults[e.target.dataset.index];
                if (result.file.type === "application/pdf") {
                    activityname = "view";
                }

                var activity = new MozActivity({
                    name: activityname,
                    data: {
                        type: result.file.type,
                        blob: result.file
                    }
                });

                activity.onerror = function () {
                    alert("Cannot preview this file.");
                };

            }, false);

            // Share button handler
            $("#sharebtn").addEventListener("click", function (e) {
                e.preventDefault();

                var result = api.searchResults[e.target.dataset.index];

                var data = { blob: result.file, number: 1 };

                if (result.file.type.indexOf("image") === 0) {
                    data.type = "image/*";
                }

                var activity = new MozActivity({
                    name: "share",
                    data: data
                });

            }, false);

            // Memory cleanup
            result = tmpl = null;

        });

    });

})();

Add preview and share handlers. Share is still broken

Signed-off-by: Kaustav Das Modak <ba1ce5b1e09413a89404909d8d270942c5dadd14@yahoo.co.in>


describe("visits-metric", function() {

  it("compiles", function() {
    expect(this.element.html()).toBeDefined();
  });

  it("has the supplied title", function() {
    expect(this.element.html()).toMatch(this.title);
  });

  describe("with a goal", function() {

    it("shows a progress bar", function() {
      expect(this.element.html()).toMatch("Goal");
      expect(this.element.find("progress").html()).toBeDefined();
      expect(this.element.html()).toMatch(/[0-9]+%/);
    });

    beforeEach(function() {
      this.goal = 12345;
    });

    beforeEach(inject(function($compile, $rootScope) {
      this.element = $compile("<metric title='" + this.title + "' goal='" + this.goal + "'></metric>")($rootScope);
      $rootScope.$digest();
    }));

  });

  beforeEach(function() {
    this.title = "Visits What Metrics";
  });

  beforeEach(module("exponeaApp"));
  beforeEach(module("directives/visits-metric.html"));

  beforeEach(inject(function($compile, $rootScope) {
    this.element = $compile("<visits-metric title='" + this.title + "'></visits-metric>")($rootScope);
    $rootScope.$digest();
  }));

});

describe("visits-metric", function() {

  it("compiles", function() {
    expect(this.element.html()).toBeDefined();
  });

  it("has the supplied title", function() {
    expect(this.element.html()).toMatch(this.title);
  });

  it("shows the last 7 days change", function() {
    expect(this.element.text()).toMatch("last 7 days");
    expect(this.element.text()).toMatch(/[+-][\s\n]+[.0-9]+%/);
  });

  describe("with a goal", function() {

    it("shows a progress bar", function() {
      expect(this.element.text()).toMatch("Goal");
      expect(this.element.find("progress").html()).toBeDefined();
      expect(this.element.text()).toMatch(/[0-9]+%/);
    });

    beforeEach(function() {
      this.goal = 12345;
    });

    beforeEach(inject(function($compile, $rootScope) {
      this.element = $compile("<visits-metric title='" + this.title + "' goal='" + this.goal + "'></visits-metric>")($rootScope);
      $rootScope.$digest();
    }));

  });

  beforeEach(function() {
    this.title = "Visits What Metrics";
  });

  beforeEach(module("exponeaApp"));
  beforeEach(module("directives/visits-metric.html"));

  beforeEach(inject(function($compile, $rootScope) {
    this.element = $compile("<visits-metric title='" + this.title + "'></visits-metric>")($rootScope);
    $rootScope.$digest();
  }));

});

Test visits last 7 days metric

Test visits last 7 days metric


'use strict';

const Worker = require('./Worker');

class FetchWorker extends Worker {

  constructor(blink) {
    super(blink);
    this.blink = blink;
  }

  setup() {
    this.queue = this.blink.queues.fetchQ;
  }

  async consume(fetchMessage) {
    const { url, options } = fetchMessage.payload.data;
    try {
      const response = await fetch(url, options);
      this.logger.info(`FetchQ.process() | ${fetchMessage.payload.meta.id} | ${response.status} ${response.statusText}`);
      this.queue.ack(fetchMessage);
      return true;
    } catch (error) {
      // Todo: retry
      this.logger.error(`FetchQ.process() | ${fetchMessage.payload.meta.id} | ${error}`);
      this.queue.nack(fetchMessage);
    }
    return false;
  }
}

module.exports = FetchWorker;

'use strict';

const logger = require('winston');

const Worker = require('./Worker');

class FetchWorker extends Worker {

  constructor(blink) {
    super(blink);
    this.blink = blink;
  }

  setup() {
    this.queue = this.blink.queues.fetchQ;
  }

  async consume(fetchMessage) {
    const { url, options } = fetchMessage.payload.data;
    try {
      const response = await fetch(url, options);
      logger.info(`FetchQ.process() | ${fetchMessage.payload.meta.id} | ${response.status} ${response.statusText}`);
      this.queue.ack(fetchMessage);
      return true;
    } catch (error) {
      // Todo: retry
      logger.error(`FetchQ.process() | ${fetchMessage.payload.meta.id} | ${error}`);
      this.queue.nack(fetchMessage);
    }
    return false;
  }
}

module.exports = FetchWorker;

Fix one missed logger instance

Fix one missed logger instance


'use strict';

import moduleName from '../../../../Frontend/js/app/angular/name';

describe('Controllers', function() {
  let $controller;
  let $rootScope;

  beforeEach(function() {
    angular.mock.module(moduleName);
  });

  beforeEach(inject(function(_$rootScope_, _$controller_) {
    $rootScope = _$rootScope_;
    $controller = _$controller_;
  }));

  describe('SayHelloController', function() {

    it('Controller is implemented', function() {
      let controller = $controller('SayHelloController', {$scope: {catchSubmit: null, name: 'test'}});

      expect(Object.keys(controller).length).toBe(2);
    });
  });
});

'use strict';

import moduleName from '../../../../Frontend/js/app/angular/name';

describe('Controllers', function() {
  let $controller;
  let $rootScope;
  let controller = null;

  beforeEach(function() {
    angular.mock.module(moduleName);
  });

  beforeEach(inject(function(_$rootScope_, _$controller_) {
    $rootScope = _$rootScope_;
    $controller = _$controller_;
  }));

  describe('SayHelloController', function() {

    it('Controller is implemented', function() {
      controller = $controller('SayHelloController', {$scope: {catchSubmit: null, name: 'test'}});

      expect(Object.keys(controller).length).toBe(2);
    });

    it('catchSubmit() method exists and executes without errors', function() {
      let actualResult = null;
      let error = null;

      try {
        actualResult = controller.catchSubmit();
      } catch (e) {
        error = e;
      }

      expect(error).toBe(null);
      expect(actualResult).toBe(undefined);
    });
  });
});

Add unit test for catchcatch() method

Add unit test for catchcatch() method


import React, { Component, PropTypes } from 'react';
import Relay from 'react-relay';
import { Link } from 'react-router';
import FontAwesome from 'react-fontawesome';
import TeamRoute from '../../relay/TeamRoute';
import teamFragment from '../../relay/teamFragment';

class TeamHeaderComponent extends Component {
  render() {
    const team = this.props.team;

    return (

        {(() => {
          if (team) {
            return (
              <Link to={'/team/' + team.dbid} className='team-header__clickable'>

                  <FontAwesome className='team-header__caret' name='angle-left' />

{team.name}

                  Team

              </Link>
            );
          }
        })()}

    );
  }
}

const TeamHeaderContainer = Relay.createContainer(TeamHeaderComponent, {
  fragments: {
    team: () => teamFragment
  }
});

class TeamHeader extends Component {
  render() {
    var route = new TeamRoute({ teamId: Checkdesk.context.team.dbid });
    return (<Relay.RootContainer Component={TeamHeaderContainer} route={route} />);
  }
}

export default TeamHeader;

import React, { Component, PropTypes } from 'react';
import Relay from 'react-relay';
import { Link } from 'react-router';
import FontAwesome from 'react-fontawesome';
import TeamRoute from '../../relay/TeamRoute';
import teamFragment from '../../relay/teamFragment';

class TeamHeaderComponent extends Component {
  render() {
    const team = this.props.team;

    return (

        {(() => {
          if (team) {
            return (
              <Link to={'/team/' + team.dbid} className='team-header__clickable'>

                  <FontAwesome className='team-header__caret' name='angle-left' />

{team.name}

                  Early Demo Team

              </Link>
            );
          }
        })()}

    );
  }
}

const TeamHeaderContainer = Relay.createContainer(TeamHeaderComponent, {
  fragments: {
    team: () => teamFragment
  }
});

class TeamHeader extends Component {
  render() {
    var route = new TeamRoute({ teamId: Checkdesk.context.team.dbid });
    return (<Relay.RootContainer Component={TeamHeaderContainer} route={route} />);
  }
}

export default TeamHeader;

Add a warning about being in early demo stage


/* @flow */

import { BUTTON_LABEL, CLASS } from '../../../constants';

export const labelStyle = `

    .${ CLASS.BUTTON }.${ CLASS.LABEL }-${ BUTTON_LABEL.CARD } {
        border-radius: 0 !important;
    }

    .${ CLASS.BUTTON }.${ CLASS.LABEL }-${ BUTTON_LABEL.CREDIT } .${ CLASS.TEXT } {
        display: none !important;
    }

    .${ CLASS.BUTTON } .${ CLASS.VAULT_LABEL } {
        max-width: 60%;
        text-overflow: ellipsis;
        overflow: hidden;
        white-space: nowrap;
    }
`;

/* @flow */

import { BUTTON_LABEL, CLASS } from '../../../constants';

export const labelStyle = `

    .${ CLASS.BUTTON }.${ CLASS.LABEL }-${ BUTTON_LABEL.CARD } {
        border-radius: 0 !important;
    }

    .${ CLASS.BUTTON } .${ CLASS.VAULT_LABEL } {
        max-width: 60%;
        text-overflow: ellipsis;
        overflow: hidden;
        white-space: nowrap;
        margin-left: 4px;
    }
`;

Add margin for vault label

Add margin for vault label


// ==UserScript==
// @name        BWI And GD SSN Registration for Stub server
// @namespace   bwin.it registration
// @description SSN Registration for Stub server
// @version     2.0.0
// @include     *.www.bwin.it/*/registration*
// @include     *qa*www.internal.giocodigitale.it/*/registration*
// @include     *dev.www.giocodigitale.it/*/registration*
// @grant       GM_xmlhttpRequest
// @require     https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.js
// ==/UserScript==

$(function () {
    if ($("#registration-form").length) {
        $('<iframe>', {
            src: "http://VIW20022:7485/",
            frameborder: 0,
            css: {
                height: 300,
                width: 300,
                position: "absolute",
                top: 0,
                left: 0
            }
        }).appendTo('div.container');

        window.addEventListener("message", receiveMessage, false);
        function receiveMessage(event) {
            if (event.origin !== "http://VIW20022:7485")
                return;
            if (event.data.message == "fillCodeFiscale") {
                $("#Input_BirthData_FiscalCode").val(event.data.value);
            }
        }
    }
});

// ==UserScript==
// @name        BWI And GD SSN Registration for Stub server
// @namespace   bwin.it registration
// @description SSN Registration for Stub server
// @version     1.1.0
// @include     *.www.bwin.it/*/registration*
// @include     *qa*www.internal.giocodigitale.it/*/registration*
// @include     *dev.www.giocodigitale.it/*/registration*
// @grant       GM_xmlhttpRequest
// @require     https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.js
// @require     https://raw.githubusercontent.com/drizet/PortalUserScripts/master/src/fiscaleGenerator/UI.js
// @require     https://raw.githubusercontent.com/drizet/PortalUserScripts/master/src/fiscaleGenerator/codeFiscaleGenerator.js
// @require     https://raw.githubusercontent.com/drizet/PortalUserScripts/master/src/core/common.js
// ==/UserScript==

$(function () {
    if ($("#registration-form").length) {
        UI.createInterface(function (codeFiscale) {
            $("#Input_BirthData_FiscalCode").val(codeFiscale);
        });
    }
});

Revert "Added new version of stub registrator."

This reverts commit f1290352576071e93fc0b543d3ae6fafd2987ead.


var expect = require('expect');
var getDays = require('../getDays');
var moment = require('moment');

describe('getDays', () => {
  it('should align weeks with the correct start days of week', () => {
    var en = moment('2015-04-10').locale('en');
    expect(getDays(en, en)[0][0].value).toEqual('29');

    var fr = moment('2015-04-10').locale('en');
    expect(getDays(fr, fr)[0][0].value).toEqual('30');
  });
});

var expect = require('expect');
var getDays = require('../getDays');
var moment = require('moment');

describe('getDays', () => {
  it('should align weeks with the correct start days of week', () => {
    var en = moment('2015-04-10').locale('en');
    expect(getDays(en, en)[0][0].formatted).toEqual('29');

    var fr = moment('2015-04-10').locale('en');
    expect(getDays(fr, fr)[0][0].formatted).toEqual('30');
  });
});

Test now failing in the correct way.

Test now failing in the correct way.


$(document).ready(function(){
	$('#form').submit(function(e){
		e.preventDefault();
		var query = $('#query').val();
		var docs = [];
		$.get('/search/query/', {query: query}, function(data){
			var result = JSON.parse(data);
			console.log(typeof result);
			for(var key in result){
				var obj = {};
				obj['doc'] = key;
				obj['score'] = result[key]
				docs.push(obj)
			}
			docs.sort(function(a, b){
				return b.score-a.score;
			});
			console.log(docs);
		});
	});
});

$(document).ready(function(){
	$('#form').submit(function(e){
		e.preventDefault();
		var query = $('#query').val();
		var docs = [];
		$.get('/search/query/', {query: query}, function(data){
			var result = JSON.parse(data);
			console.log(typeof result);
			for(var key in result){
				var obj = {};
				obj['doc'] = key;
				obj['score'] = result[key]
				docs.push(obj)
			}
			docs.sort(function(a, b){
				return b.score-a.score;
			});
			var length = docs.length;
			for(var i = 0; i < length; i++){
				var doc = docs[i];
				var row = $('

');
				var rank = $('	').text(i+1);
				var docid = $('	').text(doc['doc']);
				var score = $('	').text(doc['score']);
				row.append(rank);
				row.append(docid);
				row.append(score);
				$('#result-body').append(row);
			}
			$('#result-table').removeClass('disabled');
		});
	});
});

Add results to result table Display table when results are loaded


// ==UserScript==
// @name                Selfish Youtube
// @namespace           https://github.com/ASzc/selfish-youtube
// @description         On the watch page, remove the share panel.
// @include             http://youtube.com/watch*
// @include             http://*.youtube.com/watch*
// @include             https://youtube.com/watch*
// @include             https://*.youtube.com/watch*
// ==/UserScript==

var panelName = "action-panel-share";

// Remove panel
var actionPanelShare = document.getElementById(panelName);
actionPanelShare.parentNode.removeChild(actionPanelShare);

// Remove buttons targeting the panel
var buttons = document.body.getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) {
    var button = buttons[i];
    var buttonDTF = button.getAttribute("data-trigger-for");

    if (buttonDTF != null && buttonDTF == panelName) {
        button.parentNode.removeChild(button);
    }
}

// ==UserScript==
// @name                Selfish Youtube
// @namespace           https://github.com/ASzc/selfish-youtube
// @description         On the watch page, remove the share panel.
// @include             http://youtube.com/watch*
// @include             http://*.youtube.com/watch*
// @include             https://youtube.com/watch*
// @include             https://*.youtube.com/watch*
// ==/UserScript==

var panelName = "action-panel-share";

// Remove panel
var actionPanelShare = document.getElementById(panelName);
actionPanelShare.parentNode.removeChild(actionPanelShare);

// Remove buttons targeting the panel
var buttons = document.body.getElementsByTagName("button");
if (buttons != null && buttons instanceof NodeList) {
    for (var i = 0; i < buttons.length; i++) {
        var button = buttons[i];
        var buttonDTF = button.getAttribute("data-trigger-for");
        if (buttonDTF != null && buttonDTF == panelName) {
            button.parentNode.removeChild(button);
        }
    }
}

Make button list more fault tolerant

Make button list more fault tolerant


export function arrayToString( array ) {

	let str = '';
	for ( let i = 0, l = array.length; i < l; i ++ ) {

		str += String.fromCharCode( array[ i ] );

	}

	return str;

}

export function arrayToString( array ) {

	const utf8decoder = new TextDecoder();
	return utf8decoder.decode( array );

}

Improve the performance of attribute parsing for .b3dm

The previous implementation would use a lot of string allocations which
would cause the garbage collector to kick in often and that was a major
fit for performance.

This patch fixes the issue by using the built-in TextDecoder class, but
it might break compatibility with old browsers (e.g. Internet Explorer).


import { jsdom } from 'jsdom';

global.document = jsdom(`
    <body></body>
`);

global.window = document.defaultView;
global.navigator = window.navigator;
global.addEventListener = () => {};

Add addEventListener to test browser env setup


import Orbit from 'orbit/main';
import { Promise } from 'rsvp';
import jQuery from 'jquery';

Orbit.Promise = Promise;
Orbit.ajax = jQuery.ajax;

Orbit.pluralize = function(original) {
  return original.match(/s$/) ? original : original + 's';
};

Orbit.singularize = function(original) {
  const match = original.match(/(.*)s$/);
  return match ? match[1] : original;
};

Add Orbit.pluralize and Orbit.singularize methods for testing.


import dedent from 'dedent';
import moment from 'moment';

import { observeMethod } from '../utils/rx';

function numberWithCommas(x) {
  return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}

export default function about(app) {
  const router = app.loopback.Router();
  const User = app.models.User;
  const userCount$ = observeMethod(User, 'count');

  function showAbout(req, res, next) {
    const daysRunning = moment().diff(new Date('10/15/2014'), 'days');

    userCount$()
      .map(camperCount => numberWithCommas(camperCount))
      .doOnNext(camperCount => {
        res.render('resources/about', {
          camperCount,
          daysRunning,
          title: dedent`
            About our Open Source Community, our social media presence,
            and how to contact us`.split('\n').join(' '),
          globalCompletedCount: numberWithCommas(
            5612952 + (Math.floor((Date.now() - 1446268581061) / 2000))
          )
        });
      })
      .subscribe(() => {}, next);
  }

  router.get('/about', showAbout);
  app.use(router);
}

import dedent from 'dedent';
import moment from 'moment';

import { observeMethod } from '../utils/rx';

function numberWithCommas(x) {
  return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}

export default function about(app) {
  const router = app.loopback.Router();
  const User = app.models.User;
  const userCount$ = observeMethod(User, 'count');

  function showAbout(req, res, next) {
    const daysRunning = moment().diff(new Date('10/15/2014'), 'days');

    userCount$()
      .map(camperCount => numberWithCommas(camperCount))
      .doOnNext(camperCount => {
        res.render('resources/about', {
          camperCount,
          daysRunning,
          title: dedent`
            About our Open Source Community, our social media presence,
            and how to contact us`.split('\n').join(' '),
          globalCompletedCount: numberWithCommas(
            5612952 + (Math.floor((Date.now() - 1446268581061) / 1800))
          )
        });
      })
      .subscribe(() => {}, next);
  }

  router.get('/about', showAbout);
  app.use(router);
}

Update hard-coded rate of challenge completion to reflect recent acceleration


var redis = require("redis");

var cache = null;
var settings = null;

var pub = redis.createClient();
var sub = redis.createClient();

function init(_settings, handle) {
    settings = _settings;

    sub.on("message", function(channel, message) {
        var json = JSON.parse(message);
        if(json && json.msg && json.id !== process.pid) {
            console.log("update from " + json.id);
            handle(json.msg);
        }
    });

    sub.subscribe(settings.channel);

    return Promise.resolve();
}

function publish(message) {
    pub.publish(settings.channel, JSON.stringify({ id : process.pid, msg: message }));
}

module.exports = {
    init: init,
    publish: publish
}

var redis = require("redis");

var cache = null;
var settings = null;

var pub = null;
var sub = null;

// TODO: check what publish and subscribe return if not promissified

function init(_settings, handle) {
    return new Promise(function(resolve, reject) {
        settings = _settings;

        pub = redis.createClient();
        sub = redis.createClient();

        pub.on('error', function(err) {
            reject(err);
        });

        pub.on('ready', function() {
            if(sub !== null) {
                sub.on("message", function(channel, message) {
                    var json = JSON.parse(message);
                    if(json && json.msg && json.id !== process.pid) {
                        console.log("update from " + json.id);
                        handle(json.msg);
                    }
                });

                sub.subscribe(settings.channel);
            }

            resolve();
        });
    });
}

function publish(message) {
    pub.publish(settings.channel, JSON.stringify({ id : process.pid, msg: message }));
}

module.exports = {
    init: init,
    publish: publish
}

Handle errors if redis is not available

Handle errors if redis is not available


'use strict';

module.exports = {
  game: {
    width:  1920,
    height: 1080,
  },
};

/* eslint-env browser */

'use strict';

module.exports = {
  game: {
    width:  window.innerWidth,
    height: window.innerHeight,
  },
};

Set the width and height to the window

Set the width and height to the window


setupFlorence();

//forms field styling markup injection
//$('select:not(.small)').wrap('');
//$('select.small').wrap('');
//$('.selectbg--small:eq(1)').addClass('selectbg--small--margin');
//$('.selectbg--small:eq(3)').addClass('float-right');

Remove commented code not used.

Remove commented code not used.

Former-commit-id: e3f7d5db84e5f24f7647e03c5b1f15a5e24b2d21


'use strict';
var assert = require('assert');

describe('Invoice', function() {
  beforeEach(function(done) {
    // TODO: connect to db and provide a handle to it
    done();
  });

  it('should be able to create an invoice', function(done) {
    // TODO
    console.log('should be able to create an invoice');

    assert(false);

    done();
  });

  // TODO: test transformations etc.
});

'use strict';
var assert = require('assert');

var config = require('../config');

config.database.test.logging = noop;

var models = require('../models')(config.database.test);
var Invoice = models.Invoice;

describe('Invoice', function() {
  beforeEach(function(done) {
    // nuke possible db
    models.sequelize.sync({force: true}).finally(done);
  });

  it('should be able to create an invoice', function(done) {
    // TODO
    console.log('should be able to create an invoice');

    Invoice.create().then(function(result) {
      done();
    }).catch(done);
  });

  // TODO: test transformations etc.
});

function noop() {}

Connect to database before testing and nuke db before each


exports.up = function(knex, Promise) {
  return knex.schema.createTable('projects', function (table) {
    table.increments('id').primary()
    table.integer('tags').unsigned()
    table.foreign('tags').references('taxonomy_vocabulary.id')
    table.string('title')
    table.text('description', 'longtext')
    table.text('goals', 'longtext')
    table.text('requirements', 'longtext')
    table.text('outcome', 'longtext')
    table.string('status')
    table.dateTime('published_date')
  })
};

exports.down = function(knex, Promise) {
  return knex.schema.dropTable('projects')
};

exports.up = function (knex, Promise) {
  return knex.schema.createTable('projects', function (table) {
    table.increments('id').primary()
    table.string('type')
    table.integer('tags').unsigned()
    table.foreign('tags').references('taxonomy_vocabulary.id')
    table.string('title')
    table.string('slug').unique()
    table.integer('author').unsigned()
    table.foreign('author').references('users.id')
    table.text('description', 'longtext')
    table.text('goals', 'longtext')
    table.text('requirements', 'longtext')
    table.text('outcome', 'longtext')
    table.string('status')
    table.dateTime('published_date')
  })
}

exports.down = function (knex, Promise) {
  return knex.schema.dropTable('projects')
}

Add status, author, slug, description, goals, requirements, outcome columns to project migration file


var utils = require('../lib/utils');

module.exports = {
  ping: function(request, reply) {
    return reply('ok').code(200);
  },

  status: function(appVersion) {
    return function(request, reply) {
      var info = {
        status: 'ok',
        pid: process.pid,
        app: process.title,
        host: process.env.SMF_ZONENAME,
        uptime: process.uptime(),
        version: appVersion,
        gitHead: require('../lib/git-head')()
      };

      return reply(info).code(200);
    };
  },

  csplog: function(request, reply) {
    request.logger.warn('csp report');
    request.logger.warn(request.payload);
    return reply('ok').code(200);
  }

};

var utils = require('../lib/utils');

module.exports = {
  ping: function(request, reply) {
    return reply('ok').code(200);
  },

  status: function(appVersion) {
    return function(request, reply) {
      var info = {
        status: 'ok',
        pid: process.pid,
        app: process.title,
        host: process.env.SMF_ZONENAME,
        uptime: process.uptime(),
        version: appVersion,
        gitHead: require('../lib/git-head')()
      };

      return reply(info).code(200);
    };
  },

  csplog: function(request, reply) {
    if (!(request.payload && request.payload['csp-report'] && /^safari-extension:/.test(request.payload['csp-report']['source-file']))) {
      request.logger.warn('csp report');
      request.logger.warn(request.payload);
    }
    return reply('ok').code(200);
  }

};

Stop logging safari-extension:// CSP blocking


var cote = require('cote');

var hostname = require('os').hostname();
var source = `time-service@${hostname}`;

var timeResponder = new cote.Responder({
    name: 'Time Responder'
});

timeResponder.on('time', function(req, cb) {
    var time = new Date();
    var reply = {source, time};

    console.log(reply);
    cb(reply);
});

var cote = require('cote');

var hostname = require('os').hostname();
var source = `time-service@${hostname}`;

var timeResponder = new cote.Responder({
    name: 'Time Responder'
});

timeResponder.on('time', function(req, cb) {
    var time = new Date();
    var reply = {source, time, random: Math.random()};

    console.log(reply);
    cb(reply);
});

Add a random field to time service responses


"use strict";

var yeoman = require("yeoman-generator");
var incSay = require("incubator-say");
var util   = require("util");
var path   = require("path");

var IncubatorGenerator = module.exports = function IncubatorGenerator (args) {
	yeoman.generators.Base.apply(this, arguments);

	this.argument("appname", { type : String, required : false });
	this.appname = this.appname || path.basename(process.cwd());
	this.appname = this._.camelize(this._.slugify(this._.humanize(this.appname)));

	this.option("skip-welcome-message", {
		desc     : "Do not print out the welcome message.",
		type     : Boolean,
		defaults : true
	});

	this.option("skip-core", {
		desc     : "Skip core installation.",
		type     : Boolean,
		defaults : true
	});

	if (!this.options["skip-welcome-message"]) {
		this.log(incSay("Welcome to the Incubator Generator!"));
		this.log("By default I will create a Gruntfile and all dotfiles required to work");
		this.log("with the Bandwidth Incubator build pipeline.");
		this.log();
	}

	if (!this.options["skip-core"]) {
		this.invoke("incubator:core", args);
	}
};

util.inherits(IncubatorGenerator, yeoman.generators.Base);

"use strict";

var yeoman = require("yeoman-generator");
var incSay = require("incubator-say");
var util   = require("util");
var path   = require("path");

var IncubatorGenerator = module.exports = function IncubatorGenerator () {
	yeoman.generators.Base.apply(this, arguments);

	this.argument("appname", { type : String, required : false });
	this.appname = this.appname || path.basename(process.cwd());
	this.appname = this._.camelize(this._.slugify(this._.humanize(this.appname)));

	this.option("skip-welcome-message", {
		desc     : "Do not print out the welcome message.",
		type     : Boolean,
		defaults : false
	});

	this.option("skip-core", {
		desc     : "Skip core installation.",
		type     : Boolean,
		defaults : false
	});
};

util.inherits(IncubatorGenerator, yeoman.generators.Base);

IncubatorGenerator.prototype.greeting = function () {
	if (!this.options["skip-welcome-message"]) {
		this.log(incSay("Welcome to the Incubator Generator!"));
		this.log("By default I will create a Gruntfile and all dotfiles required to work");
		this.log("with the Bandwidth Incubator build pipeline.");
		this.log();
	}
};

IncubatorGenerator.prototype.components = function () {
	if (!this.options["skip-core"]) {
		this.invoke("incubator:core", this.args);
	}
};

Fix bug that prevents `yo incubator` from working


// --- Dependencies
import * as React from 'react'
import { cx } from 'emotion'

// --- Images
import LogoSrc from '../images/logo-spark-footer.svg'

// --- Utils
import styles from '../utils/styles'

/**
 * Helpers
 */

const getYear = () => {
  const date = new Date()

  return date.getFullYear()
}

/**
 * Component
 */

const Footer = ({ hasSidebar }) => (

    Designed and developed by

      © Spree Commerce. {getYear()} All Rights Reserved.

)

export default Footer

// --- Dependencies
import * as React from 'react'
import { cx } from 'emotion'

// --- Images
import LogoSrc from '../images/logo-spark-footer.svg'

// --- Utils
import styles from '../utils/styles'

/**
 * Helpers
 */

const getYear = () => {
  const date = new Date()

  return date.getFullYear()
}

/**
 * Component
 */

const Footer = ({ hasSidebar }) => (

    Designed and developed by

      ©{' '}

        Spree Commerce.
      {' '}
      {getYear()} All Rights Reserved.

)

export default Footer

Correct footer links for guides

Correct footer links  for guides


import Ember from 'ember';
import {
  moduleFor,
  test
} from 'ember-qunit';

moduleFor('service:i18n', {
  // Specify the other units that are required for this test.
  // needs: ['service:foo']
});

test('it exists', function(assert) {
  var service = this.subject();
  assert.ok(service);
});

test('throws when action names are blank', function (assert) {
  var service = this.subject();

  assert.throws(function () { service.registerPreInitAction('', function () {}); },
    'Throws if pre-init action name is blank.');
  assert.throws(function () { service.registerPostInitAction('', function () {}); },
    'Throws if post-init action name is blank.');
});

test('throws when actions are not functions', function (assert) {
  var service = this.subject();

  assert.throws(function () { service.registerPreInitAction('woo', 'woo'); },
   'Throws if pre-init action is not a function.');
  assert.throws(function () { service.registerPostInitAction('woo', 'hoo'); },
    'Throws if post-init action is not a function.');
});

Remove import for debug code

Remove import for debug code

Remove an import added during debugging.


'use strict';

module.exports = {
  protocol: 'https://',
  host: 'tartan.plaid.com',
  endpoint: {
    get: {
      method: 'GET',
      route: 'connect'
    },
    submit: {
      method: 'POST',
      route: 'connect'
    },
    step: {
      method: 'POST',
      route: 'connect/step'
    },
    remove: {
      method: 'DELETE',
      route: 'connect'
    },
    patch: {
      method: 'PATCH',
      rooute: 'connect'
    }
  }
};

'use strict';

module.exports = {
  protocol: 'https://',
  host: 'tartan.plaid.com',
  endpoint: {
    get: {
      method: 'GET',
      route: 'connect'
    },
    submit: {
      method: 'POST',
      route: 'connect'
    },
    step: {
      method: 'POST',
      route: 'connect/step'
    },
    remove: {
      method: 'DELETE',
      route: 'connect'
    },
    patch: {
      method: 'PATCH',
      route: 'connect'
    }
  }
};

Fix typo for route property.

Fix typo for route property.


var mpdProcess = require('./mpd-process.js'),
    mpdClient = require('./mpd-client.js');

exports.instance = function (mpdConfig, radiodanConfig) {
  var instance = {},
      client;

  instance.start = function () {
    var process = mpdProcess.create(mpdConfig),
        onStart = process.start();

    client = mpdClient.create(radiodanConfig, process.port);

    client.connect(onStart);
    client.playAllRandom();
  };

  return instance;
};

var mpdProcess = require('./mpd-process.js'),
    mpdClient = require('./mpd-client.js');

exports.instance = function (mpdConfig, radiodanConfig) {
  var instance = {},
      client;

  instance.start = function () {
    var process = mpdProcess.create(mpdConfig),
        onStart = process.start();

    client = mpdClient.create(radiodanConfig, process.port);

    client.connect(onStart);
    client.playAllRandom();
    instance.client = client;
  };

  return instance;
};

Add mpd client as a publicly acessible object


"use strict";

(function () {
    angular
        .module("argo")
        .config(config)
        .run(setup);

    config.$inject = ["$stateProvider", "$urlRouterProvider"];
    function config($stateProvider, $urlRouterProvider) {
        $stateProvider
            .state("index", {
                abstract: true,
                url: "/",
                views: {
                    "default": {
                        templateUrl: "views/default.html",
                        controller: "DefaultCtrl",
                        controllerAs: "default"
                    }
                }
            })
            .state("index.subs", {
                url: "",
                views: {
                    "account@index": {
                        templateUrl: "views/account.html"
                    }
                }
            });

        $urlRouterProvider.otherwise("/");
    }

    setup.$inject = ["$state"];
    function setup($state) {
        $state.transitionTo("index.subs");
    }

}());

"use strict";

(function () {
    angular
        .module("argo")
        .config(config)
        .run(setup);

    config.$inject = ["$stateProvider", "$urlRouterProvider"];
    function config($stateProvider, $urlRouterProvider) {
        $stateProvider
            .state("default", {
                abstract: true,
                url: "/",
                templateUrl: "views/default.html",
                controller: "DefaultCtrl",
                controllerAs: "default"
            })
            .state("default.subs", {
                views: {
                    "header": {
                        templateUrl: "views/header.html"
                    },
                    "trades": {
                        templateUrl: "views/trades.html"
                    },
                    "orders": {
                        templateUrl: "views/orders.html"
                    },
                    "positions": {
                        templateUrl: "views/positions.html"
                    },
                    "exposure": {
                        templateUrl: "views/exposure.html"
                    },
                    "activity": {
                        templateUrl: "views/activity.html"
                    },
                    "news": {
                        templateUrl: "views/news.html"
                    },
                    "account": {
                        templateUrl: "views/account.html"
                    },
                    "quotes": {
                        templateUrl: "views/quotes.html"
                    },
                    "charts": {
                        templateUrl: "views/charts.html"
                    }
                }
            });

        $urlRouterProvider.otherwise("/");
    }

    setup.$inject = ["$state"];
    function setup($state) {
        $state.transitionTo("default.subs");
    }

}());

Add sub views to the router.

Add sub views to the router.


var Template = module.exports = function(fn, args) {
  this.fn = fn ? recoverFn(fn.toString()) : null
  this.args = args || []
}

Template.prototype.$serialize = function() {
  return {
    fn: this.fn.toString(),
    args: this.args
  }
}

Template.prototype.$deserialize = function(obj) {
  obj.fn = recoverFn(obj.fn)
  return obj
}

recoverFn = function(str) {
  var fn
  eval('fn = ' + str)
  return fn
}

require('./implode').register('Template', Template, ['fn', 'args'])

var Template = module.exports = function(fn, args) {
  this.source = fn ? fn.toString() : null
  this.fn = this.source ? recoverFn(this.source) : null
  this.args = args || []
}

Template.prototype.$deserialize = function(obj) {
  obj.fn = recoverFn(obj.source)
  return obj
}

recoverFn = function(str) {
  var lines = str.split('\n')
  lines.splice(1, 0, 'var _d = typeof _b === "undefined";'
                   , 'return (function inner(_b, _d) {'
                   , 'Object.keys(inner.caller).forEach(function(key) { inner[key] = inner.caller[key]; });'
                   , 'with(this) {')
  lines.splice(lines.length - 1, 0
                   , '}'
                   , 'if (!_d) return "";'
                   , 'return _b.join("");'
                   , '}).call(typeof _t !== "undefined" ? _t : this, _d ? [] : _b, _d)')
  var fn
  eval('fn = ' + lines.join('\n'))
  return fn
}

require('./implode').register('Template', Template, ['source', 'args'])

Move Extended Template Funciton Logic into Template Object


import React, {Component} from 'react';
import {DragDropContext} from 'react-dnd';
import {Col} from 'react-bootstrap';
import HTML5Backend from 'react-dnd-html5-backend';

import List from './List';
import ElementDetails from './ElementDetails';
import ElementStore from './stores/ElementStore';

class Elements extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currentElement: null,
    };
    this.handleOnChange = this.handleOnChange.bind(this)
  }

  componentDidMount() {
    ElementStore.listen(this.handleOnChange);
  }

  componentWillUnmount() {
    ElementStore.unlisten(this.handleOnChange);
  }

  handleOnChange(state) {
    const { currentElement } = state;
    this.setState({ currentElement });
  }

  render() {
    const { currentElement } = this.state;
    const showReport = (currentElement || []).type === 'report'

    let list = (
      <Col md={12} style={{paddingLeft: "10px"}}>
        <List overview={false} showReport={showReport}/>
      </Col>
    )
    let page = (<span />)

    if (currentElement) {
      list = (
        <Col md={4}>
          <List overview={false} showReport={showReport}/>
        </Col>
      )
      page = (
        <Col md={8}>
          <ElementDetails currentElement={currentElement} />
        </Col>
      )
    }

    return (
      <div>
        {list}
        {page}
      </div>
    );
  }
}

export default DragDropContext(HTML5Backend)(Elements);

import React, {Component} from 'react';
import {DragDropContext} from 'react-dnd';
import {Col} from 'react-bootstrap';
import HTML5Backend from 'react-dnd-html5-backend';

import List from './List';
import ElementDetails from './ElementDetails';
import ElementStore from './stores/ElementStore';

class Elements extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currentElement: null,
    };
    this.handleOnChange = this.handleOnChange.bind(this)
  }

  componentDidMount() {
    ElementStore.listen(this.handleOnChange);
  }

  componentWillUnmount() {
    ElementStore.unlisten(this.handleOnChange);
  }

  handleOnChange(state) {
    const { currentElement } = state;
    this.setState({ currentElement });
  }

  render() {
    const { currentElement } = this.state;
    const showReport = (currentElement || []).type === 'report'

    let list = (
      <Col md={12} style={{paddingLeft: "10px"}}>
        <List overview={true} showReport={showReport}/>
      </Col>
    )
    let page = (<span />)

    if (currentElement) {
      list = (
        <Col md={4}>
          <List overview={false} showReport={showReport}/>
        </Col>
      )
      page = (
        <Col md={8}>
          <ElementDetails currentElement={currentElement} />
        </Col>
      )
    }

    return (
      <div>
        {list}
        {page}
      </div>
    );
  }
}

export default DragDropContext(HTML5Backend)(Elements);

Hide Drag-Drop when no ElementDetails

Hide Drag-Drop when no ElementDetails


window.m = angular.mock.module;
window.compileHtml = function(htmlStr, data, parentElement = null) {
    data = data || {};
    var $scope;
    inject(function($compile, $rootScope) {
        parentElement = parentElement || document.body;
        angular.element(parentElement).html(htmlStr);
        $scope = $rootScope.$new();
        angular.extend($scope, data);
        $compile(parentElement)($scope);
        $scope.$digest();
    });
    return $scope;
}

window.compileDirective = function(directive, attr, parentElement = null) {
    let attributes = '';
    for (let key in attr) {
        let kebab = _.kebabCase(key);
        if (key.startsWith('@')) {
            attributes += ` ${kebab}="{{ data['${key}'] }}"`;
        }
        else {
            attributes += ` ${kebab}="data.${key}"`;
        }
    }
    let scope = compileHtml(`<${directive} ${attributes}></${directive}>`, {data: attr}, parentElement);
    scope.$digest();

    return scope;
}

function angularEquality(first, second) {
  return angular.equals(first, second);
}

window.useAngularEquality = function() {
    beforeEach(function() {
      jasmine.addCustomEqualityTester(angularEquality);
    });
}

window.waitForPromise = function() {
    setInterval(()=>{
        inject(function($rootScope) {
            $rootScope.$apply();
        });
    },0)
}

window.m = angular.mock.module;
window.compileHtml = function(htmlStr, data, parentElement = null) {
    data = data || {};
    var $scope;
    inject(function($compile, $rootScope) {
        parentElement = parentElement || document.body;
        angular.element(parentElement).html(htmlStr);
        $scope = $rootScope.$new();
        angular.extend($scope, data);
        $compile(parentElement)($scope);
        $scope.$digest();
    });
    return $scope;
}

window.compileDirective = function(directive, attr, parentElement = null) {
    let attributes = '';
    for (let key in attr) {
        let kebab = _.kebabCase(key);
        if (key.startsWith('@')) {
            attributes += ` ${kebab}="{{ data['${key}'] }}"`;
        }
        else if (key.startsWith('&')) {
            attributes += ` ${kebab}="data['${key}']()"`;
        }
        else {
            attributes += ` ${kebab}="data.${key}"`;
        }
    }
    let scope = compileHtml(`<${directive} ${attributes}></${directive}>`, {data: attr}, parentElement);
    scope.$digest();

    return scope;
}

function angularEquality(first, second) {
  return angular.equals(first, second);
}

window.useAngularEquality = function() {
    beforeEach(function() {
      jasmine.addCustomEqualityTester(angularEquality);
    });
}

window.waitForPromise = function() {
    setInterval(()=>{
        inject(function($rootScope) {
            $rootScope.$apply();
        });
    },0)
}

spec-helper: Add support for function call attribute (&)


// Define search and replace terms for image src link
var search = "s100";
var replace = "s512";

function getProfileImageContainer() {
    // Get profile image containers
    return $(".channel-header-profile-image-container");
}

function getProfileImage(container) {
    // Get profile image tag
    return container.find("img");
}

function injectEnlargeLink() {
    // Get profile image link
    var imageContainer = getProfileImageContainer();

    // No image?
    if (!imageContainer.length) {
        return;
    }

    // Get img tag
    var imageTag = getProfileImage(imageContainer);

    // No tag?
    if (!imageTag.length) {
        return;
    }

    // Get image src
    var src = imageTag.attr('src');

    // Replace image pixel value in URL
    src = src.replace(search, replace);

    // Set link to point to the larger image
    imageContainer.attr('href', src);

    // Open in a new tab
    imageContainer.attr('target', '_blank');
}

// One-time injection
injectEnlargeLink();

Clean up comments & reformat code

Clean up comments & reformat code


const notify = require('gulp-notify');

module.exports = () => {
  const args = Array.prototype.slice.call(arguments);

  // Send error to notification center with gulp-notify
  notify.onError({
    title: 'Compile Error',
    message: '<%= error.message %>',
  }).apply(this, args);

  // Keep gulp from hanging on this task
  this.emit('end');
};

const notify = require('gulp-notify');

module.exports = function () {
  const args = Array.prototype.slice.call(arguments);

  // Send error to notification center with gulp-notify
  notify.onError({
    title: 'Compile Error',
    message: '<%= error.message %>',
  }).apply(this, args);

  // Keep gulp from hanging on this task
  this.emit('end');
};

Fix bound function in gulp error log

Fix bound function in gulp error log


// Update the local timeline from a Meteor server
// XXX: If @a is subscribed to @b, @a should get @b's notices in @a's Personal timeline.
//      Do Replies timeline.

var MeteorUpdater = function()
{
    return {

        init: function(server, port, timeline)
        {
            Meteor.callbacks["process"] = function(data) {
                var d = JSON.parse(data);

                $user_url = $('address .url')[0].href+d['user']['screen_name'];

                if (timeline == 'public' ||
                    $user_url+'/all' == window.location.href ||
                    $user_url == window.location.href) {

                    RealtimeUpdate.receive(d);
                }
            };

            Meteor.host = server;
            Meteor.port = port;
            Meteor.joinChannel(timeline, 0);
            Meteor.connect();
        }
    }
}();

// Update the local timeline from a Meteor server
// XXX: If @a is subscribed to @b, @a should get @b's notices in @a's Personal timeline.
//      Do Replies timeline.

var MeteorUpdater = function()
{
    return {

        init: function(server, port, timeline)
        {
            var screen_name;

            Meteor.callbacks["process"] = function(data) {
                var d = JSON.parse(data);
                screen_name = d['user']['screen_name'];

                if (timeline == 'public' ||
                    $('address .url')[0].href+screen_name+'/all' == window.location.href ||
                    $('address .url')[0].href+screen_name == window.location.href) {
                    RealtimeUpdate.receive(d);
                }
            };

            Meteor.host = server;
            Meteor.port = port;
            Meteor.joinChannel(timeline, 0);
            Meteor.connect();
        }
    }
}();

Revert "Made it slighly more compact with less jQuery selection"

This reverts commit c9bc3900909a85b9b4be31c4dac7f809127a8bf0.


'use strict'

const util = require('gulp-util')
const path = require('path')

exports.fn = function (gulp, paths, mode, done) {
  if (mode.production) {
    util.log('Skipping \'' + util.colors.cyan('gulp-htmlhint') + '\'')
    done()
    return
  }

  var consoleReporter = function (results) {
    results.htmlhint.messages.forEach(function (warning) {
      var file = path.relative(path.join(process.cwd(), paths.src), warning.file)
      var message = '[' + util.colors.cyan('lint') + '] ' +
        util.colors.red(warning.error.type) + ' ' + file +
        '[' + warning.error.line + ', ' + warning.error.col + ']: ' + warning.error.message
      util.log(message)
    })
  }

  var htmlhint = require('gulp-htmlhint')
  return gulp.src([paths.src + '**/*.html', '!' + paths.jspmPackages])
    .pipe(htmlhint())
    .pipe(htmlhint.reporter(consoleReporter))
}

'use strict'

const util = require('gulp-util')
const path = require('path')

exports.fn = function (gulp, paths, mode, done) {
  if (mode.production) {
    util.log('Skipping \'' + util.colors.cyan('gulp-htmlhint') + '\'')
    done()
    return
  }

  var consoleReporter = function (results) {
    results.htmlhint.messages.forEach(function (warning) {
      var file = path.relative(path.join(process.cwd(), paths.src), warning.file)
      var message = '[' + util.colors.cyan('lint') + '] ' +
        util.colors.red(warning.error.type) + ' ' + file +
        '[' + warning.error.line + ', ' + warning.error.col + ']: ' + warning.error.message
      util.log(message)
    })
  }

  var htmlhint = require('gulp-htmlhint')
  return gulp.src([paths.src + '**/*.html', '!' + paths.jspmPackages])
    .pipe(htmlhint('.htmlhintrc'))
    .pipe(htmlhint.reporter(consoleReporter))
}

Use our linting rules in gulp.

Use our linting rules in gulp.


Application.Services.factory('Events',  ['$filter', EventsService]);

function EventsService($filter) {
    var service = {
        date: '',

        addConvertedTime: function (project) {
            project.reviews = service.update(project.open_reviews);
            project.samples = service.update(project.samples);
            project.processes = service.update(project.processes);
            project.notes = service.update(project.notes);
            project.drafts = service.update(project.drafts);
            return project;
        },

        update: function (items) {
            items.forEach(function (item) {
                var item_date = new Date(0);
                item_date.setUTCSeconds(item.mtime.epoch_time);
                item.converted_mtime = Date.UTC(item_date.getUTCFullYear(), item_date.getUTCMonth(), item_date.getUTCDay());
            });
            return items;
        },

        prepareCalendarEvent: function (items) {
            var calendar_event = [];
            if (items.length !== 0) {
                var grouped_by_convertedtime = $filter('groupBy')(items, 'converted_mtime');
                Object.keys(grouped_by_convertedtime).forEach(function (key) {
                    var d = new Date(0);
                    var value = grouped_by_convertedtime[key][0];
                    d.setUTCSeconds(value.mtime.epoch_time);
                    calendar_event.push({title: value.title, start: d});
                });
                return calendar_event;
            }

        },

        updateDate: function (project, date) {
            project.date = date;
            return project;
        }
    };
    return service;
}

Application.Services.factory('Events',  ['$filter', EventsService]);

function EventsService($filter) {
    var service = {
        date: '',

        addConvertedTime: function (project) {
            project.reviews = service.update(project.open_reviews);
            project.samples = service.update(project.samples);
            project.processes = service.update(project.processes);
            project.notes = service.update(project.notes);
            project.drafts = service.update(project.drafts);
            return project;
        },

        update: function (items) {
            items.forEach(function (item) {
                var item_date = new Date(0);
                item_date.setUTCSeconds(item.mtime.epoch_time);
                item.converted_mtime = Date.UTC(item_date.getUTCFullYear(), item_date.getUTCMonth(), item_date.getUTCDay());
            });
            return items;
        },

        prepareCalendarEvent: function (items) {
            var calendar_event = [];
            if (items.length !== 0) {
                var grouped_by_convertedtime = $filter('groupBy')(items, 'converted_mtime');
                Object.keys(grouped_by_convertedtime).forEach(function (key) {
                    var d = new Date(0);
                    var value = grouped_by_convertedtime[key][0];
                    d.setUTCSeconds(value.mtime.epoch_time);
                    calendar_event.push({title: value.title, start: d});
                });
            }
            return calendar_event;
        },

        updateDate: function (project, date) {
            project.date = date;
            return project;
        }
    };
    return service;
}

Fix method to always return a value.

Fix method to always return a value.


//
// Reddit UI Data Parser
//
// Parses log data provided to UI frontent into content that can be rendered.
//

const redditParser = (chartName, chartData, callback) => {
    //We are collecting one metric:
    //    Posts in past 72 hours

    let dataset = {
        datasets: [
            { label: "posts (past 72 hours)", data: [] },
        ]
    }

    //This is the data for the chart.
    for (let dataPoint of chartData){
        dataset.datasets[0].data.push({
            x: dataPoint.date,
            y: dataPoint.data.children.reduce((p, c) =>
                moment(c.data.created_utc*1000).isAfter(moment(dataPoint.date).subtract(3, 'days')) ? p+1 : p, 0)
        })
    }

    //This is the data for the overview bar.
    const latestData = chartData[chartData.length-1] || {}
    dataset.currentData = {
        "posts (past 72 hours)": latestData.data.children.reduce((p, c) =>
            moment(c.data.created_utc*1000).isAfter(moment().subtract(3, 'days')) ? p+1 : p, 0)
    }

    let chartStyle = JSON.parse(JSON.stringify(defaultChartOptions))
    callback(addChartStyle(dataset), chartStyle)
}

//
// Reddit UI Data Parser
//
// Parses log data provided to UI frontent into content that can be rendered.
//

const redditParser = (chartName, chartData, callback) => {
    //We are collecting one metric:
    //    Posts in past 72 hours

    let dataset = {
        datasets: [
            { label: "posts (past 72 hours)", data: [] },
        ]
    }

    //This is the data for the chart.
    for (let dataPoint of chartData){
        let isThreeDaysOld = undefined
        if (dataPoint.data !== undefined){
            isThreeDaysOld = dataPoint.data.children.reduce((p, c) =>
                moment(c.data.created_utc*1000).isAfter(moment(dataPoint.date).subtract(3, 'days')) ? p+1 : p, 0)
        }
        dataset.datasets[0].data.push({
            x: dataPoint.date,
            y: isThreeDaysOld
        })
    }

    //This is the data for the overview bar.
    const latestData = chartData[chartData.length-1] || {}
    let postsLastThreeDays = undefined
    if (latestData.data){
        postsLastThreeDays = latestData.data.children.reduce((p, c) =>
            moment(c.data.created_utc*1000).isAfter(moment().subtract(3, 'days')) ? p+1 : p, 0)
    }
    dataset.currentData = {
        "posts (past 72 hours)": postsLastThreeDays
    }

    let chartStyle = JSON.parse(JSON.stringify(defaultChartOptions))
    callback(addChartStyle(dataset), chartStyle)
}

Fix reddit parsing in case error is returned/data is unavailable