/* jshint node: true */

module.exports = function(environment) {
  var ENV = {
    modulePrefix: 'dummy',
    environment: environment,
    baseURL: '/',
    locationType: 'auto',
    EmberENV: {
      FEATURES: {
        // Here you can enable experimental features on an ember canary build
        // e.g. 'with-controller': true
      }
    },

    APP: {
      // Here you can pass flags/options to your application instance
      // when it is created
    }
  };

  if (environment === 'development') {
    // ENV.APP.LOG_RESOLVER = true;
    // ENV.APP.LOG_ACTIVE_GENERATION = true;
    // ENV.APP.LOG_TRANSITIONS = true;
    // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
    // ENV.APP.LOG_VIEW_LOOKUPS = true;
  }

  if (environment === 'test') {
    // Testem prefers this...
    ENV.baseURL = '/';
    ENV.locationType = 'none';

    // keep test console output quieter
    ENV.APP.LOG_ACTIVE_GENERATION = false;
    ENV.APP.LOG_VIEW_LOOKUPS = false;

    ENV.APP.rootElement = '#ember-testing';
  }

  if (environment === 'production') {
    ENV.baseURL = '/ember-css-transitions'
  }

  return ENV;
};

/* jshint node: true */

module.exports = function(environment) {
  var ENV = {
    modulePrefix: 'dummy',
    environment: environment,
    baseURL: '/',
    locationType: 'hash',
    EmberENV: {
      FEATURES: {
        // Here you can enable experimental features on an ember canary build
        // e.g. 'with-controller': true
      }
    },

    APP: {
      // Here you can pass flags/options to your application instance
      // when it is created
    }
  };

  if (environment === 'development') {
    // ENV.APP.LOG_RESOLVER = true;
    // ENV.APP.LOG_ACTIVE_GENERATION = true;
    // ENV.APP.LOG_TRANSITIONS = true;
    // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
    // ENV.APP.LOG_VIEW_LOOKUPS = true;
  }

  if (environment === 'test') {
    // Testem prefers this...
    ENV.baseURL = '/';
    ENV.locationType = 'none';

    // keep test console output quieter
    ENV.APP.LOG_ACTIVE_GENERATION = false;
    ENV.APP.LOG_VIEW_LOOKUPS = false;

    ENV.APP.rootElement = '#ember-testing';
  }

  if (environment === 'production') {
    ENV.baseURL = '/ember-css-transitions'
  }

  return ENV;
};

Use hash based location type for now to support IE9 / ember bug


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

(function(global) {
  // Use a cache for the Picasa API response for the Google I/O 2014 album.
  global.shed.router.get('/data/feed/api/user/111395306401981598462/albumid/6029456067262589905(.*)',
    global.shed.cacheFirst, {origin: /https?:\/\/picasaweb.google.com/});
  // Use a cache for the actual image files as well.
  global.shed.router.get('/(.+)', global.shed.cacheFirst, {origin: /https?:\/\/lh\d*.googleusercontent.com/});
})(self);

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

(function(global) {
  // Use a cache for the Picasa API response for the Google I/O 2014 album.
  global.shed.router.get('/data/feed/api/user/(.*)',
    global.shed.networkFirst, {origin: /https?:\/\/picasaweb.google.com/});
  // Use a cache for the actual image files as well.
  global.shed.router.get('/(.+)', global.shed.networkFirst,
    {origin: /https?:\/\/lh\d*.googleusercontent.com/});
})(self);

Use a networkFirst strategy for Picasa


// Auto-magically referenced. Yay.

$(document).ready(function() {
  var rubyFolders = gon.folders;
  var folders = {};

  var currentId = gon.currentFolder.id;

  // Convert all the folders to objects.
  rubyFolders.forEach(function(folder) {
    folders[folder.id] = {
      id: folder.id,
      label: folder.name,
      parentId: folder.parent_folder_id,
      children: []
    };
  });

  var treeData = [];
  var rootFolder;

  // Generate the tree we need for jqTree.
  for (var id in folders) {
    var folder = folders[id];
    var parentId = folder.parentId;
    if (!parentId) {
      rootFolder = folder;
    } else {
      folders[parentId].children.push(folder);
    }
  }
  treeData.push(rootFolder);

  // Set up the tree.
  var treeElement = $('#file-tree');
  treeElement.tree({
    data: treeData,
    autoOpen: true
  });

  // Select the folder we're currently in.
  var currentFolderNode = treeElement.tree('getNodeById', currentId);
  treeElement.tree('selectNode', currentFolderNode);

  // Set up bindings on the tree.
  treeElement.bind('tree.select', function(event) {
    var selectedNode = treeElement.tree('getSelectedNode');
    var selectedFolderId = selectedNode.id;

  });
});

// Auto-magically referenced. Yay.

$(document).ready(function() {
  var rubyFolders = gon.folders;
  var folders = {};

  var currentId = gon.currentFolder.id;

  // Convert all the folders to objects.
  rubyFolders.forEach(function(folder) {
    var nameAndCount = folder.name + " (" + folder.count + ")";
    folders[folder.id] = {
      id: folder.id,
      label: nameAndCount,
      url: folder.url,
      parentId: folder.parent_folder_id,
      children: []
    };
  });

  var treeData = [];
  var rootFolder;

  // Generate the tree we need for jqTree.
  for (var id in folders) {
    var folder = folders[id];
    var parentId = folder.parentId;
    if (!parentId) {
      rootFolder = folder;
    } else {
      folders[parentId].children.push(folder);
    }
  }
  treeData.push(rootFolder);

  // Set up the tree.
  var treeElement = $('#file-tree');
  treeElement.tree({
    data: treeData,
    autoOpen: true
  });

  // Select the folder we're currently in.
  var currentFolderNode = treeElement.tree('getNodeById', currentId);
  treeElement.tree('selectNode', currentFolderNode);

  // Set up bindings on the tree.
  treeElement.bind('tree.select', function(event) {
    var selectedNode = treeElement.tree('getSelectedNode');
    var selectedFolderUrl = selectedNode.url;

    window.location.href = selectedFolderUrl;
  });
});

Add folder content count to label, navigate to folder on click.


'use strict';

angular.module('<%= scriptAppName %>')
  .factory('<%= cameledName %>', function () {
    // Service logic
    // ...

    var meaningOfLife = 42;

    // Public API here
    return {
      someMethod: function () {
        return meaningOfLife;
      }
    };
  });

'use strict';
(function(){

  var <%= cameledName %> = function(){

    var meaningOfLife = 42;

    // Public API here
    return {
      someMethod: someMethod
    };

    //////////////

    function someMethod() {
      return meaningOfLife;
    }
  };

  angular
    .module('<%= scriptAppName %>')
    .factory('<%= cameledName %>', <%= cameledName %>);

}).call(this);

Refactor Javascript Factory to match best practices


'use strict';

/**
 * @ngdoc function
 * @name MatchCalendarApp.controller:SettingsCtrl
 * @description
 * # SettingsCtrl
 * Controller of the MatchCalendarApp
 */
angular.module('MatchCalendarApp')
    .controller('SettingsCtrl', function ($scope, NotifcationTimeFormat, $localForage, $modal, $window) {
        $scope.addSubreddit = function (name) {
            if (name === '' || name === null || name === undefined) {
                return;
            }
            if ($scope.settings.subreddits.indexOf(name) === -1) {
                $scope.settings.subreddits.push(name);
            }
        };
        $scope.removeSubreddit = function (index) {
            $scope.settings.subreddits.splice(index, 1);
        };
        $scope.removeNotificationTime = function (index) {
            $scope.settings.notificationTimes.splice(index, 1);
        };
        $scope.newNotificationTime = function () {
            $scope.settings.notificationTimes.push({value: 600});
        };
        $scope.translateSeconds = function (duration) {
            return NotifcationTimeFormat.translateSeconds(duration);
        };

        $scope.clearStorage = function() {
            $localForage.clear().then(function() {
                $window.location.reload();
            }).fail(function() {
                $modal.open({
                    template: 'Failed to clear storage. You may need to clear it manually'
                });
            });
        };
    });

'use strict';

/**
 * @ngdoc function
 * @name MatchCalendarApp.controller:SettingsCtrl
 * @description
 * # SettingsCtrl
 * Controller of the MatchCalendarApp
 */
angular.module('MatchCalendarApp')
    .controller('SettingsCtrl', function ($scope, NotifcationTimeFormat, $localForage, $modal, $window) {
        $scope.addSubreddit = function (name) {
            if (name === '' || name === null || name === undefined) {
                return;
            }
            if ($scope.settings.subreddits.indexOf(name) === -1) {
                $scope.settings.subreddits.push(name);
            }
        };
        $scope.removeSubreddit = function (index) {
            $scope.settings.subreddits.splice(index, 1);
        };
        $scope.removeNotificationTime = function (index) {
            $scope.settings.notificationTimes.splice(index, 1);
        };
        $scope.newNotificationTime = function () {
            $scope.settings.notificationTimes.push({value: 600});
        };
        $scope.translateSeconds = function (duration) {
            return NotifcationTimeFormat.translateSeconds(duration);
        };

        $scope.clearStorage = function() {
            $localForage.clear().then(
                function() {
                    $window.location.reload();
                },
                function() {
                    $modal.open({
                        template: 'Failed to clear storage. You may need to clear it manually'
                    });
                }
            );
        };
    });

Use promises correctly, fixes storage clearing error display


var gulp = require('gulp')
var source = require('vinyl-source-stream')
var browserify = require('browserify')
var babel = require('babelify')
var glob = require('glob')
var path = require('path')
var del = require('del')

var OUT_DIR = './lib'

function bundle (indexFile, dir, deps) {
  var bundler = browserify(indexFile, { debug: true }).transform(babel)
  bundler.bundle()
    .pipe(source('bundle.js'))
    .pipe(gulp.dest(dir))
  for (var key in deps) {
    gulp.src(glob.sync(key))
      .pipe(gulp.dest(path.join(dir, deps[key])))
  }
}

gulp.task('clean', function () {
  del(OUT_DIR)
})

gulp.task('build', function () {
  bundle('./src/index.js', OUT_DIR, {
    './plugin/*': '',
    './plugin/images/*': 'images/'
  })
})

gulp.task('default', ['clean', 'build'])

var gulp = require('gulp')
var source = require('vinyl-source-stream')
var browserify = require('browserify')
var babel = require('babelify')
var glob = require('glob')
var path = require('path')
var del = require('del')

var OUT_DIR = './lib/'

function bundle (indexFile, dir, deps) {
  var bundler = browserify(indexFile, { debug: true }).transform(babel)
  bundler.bundle()
    .pipe(source('bundle.js'))
    .pipe(gulp.dest(dir))
  for (var key in deps) {
    gulp.src(glob.sync(key))
      .pipe(gulp.dest(path.join(dir, deps[key])))
  }
}

gulp.task('clean', function () {
  del(OUT_DIR)
})

gulp.task('build', function () {
  bundle('./src/index.js', OUT_DIR, {
    './plugin/*': '',
    './plugin/images/*': 'images/'
  })
})

gulp.task('default', ['clean', 'build'])

Add trailing path delimeter onto OUT_DIR


var gulp = require('gulp');
var del = require('del');

gulp.task('clean', function (done) {
	var dir = './source';
	var jsFiles = '/**/*.js';
	var mapFiles = '/**/*.js.map';
	var typingFiles = '/**/*.d.ts';
	return del([dir + jsFiles, dir + mapFiles, dir + typingFiles], done);
});

var gulp = require('gulp');
var clean = require('./source/services/gulp/clean');

clean.config(null, gulp);

Use the new clean file


forClass("com.android.internal.policy.impl.LockScreen", {
  onViewFocused: function() {
    return true;
  }
});

forClass("com.android.internal.policy.impl.KeyguardViewManager$KeyguardViewHost", {
  onWindowStateChanged: function() {
    return true;
  }
});

forClass("com.android.internal.policy.impl.LockPatternKeyguardView", {
  onViewFocused: function() {
    return true;
  }
});

forClass("com.android.server.status.StatusBarService$ExpandedDialog", {
  onWindowStateChanged: function() {
    speak("Notifications", true);
    nextShouldNotInterrupt();
    return true;
  }
});

Make expanding notification bar much less verbose.


debug_log("Creating click handler");
$("body").on("click", "a", function(event) {
	debug_log("Click handler activated.")
	var url = $(this).prop("href");
	debug_log("URL: "+url)
	if (ExtensionConfig.handle_magnets) {
		debug_log("Handling magnets enabled");
		if (url.indexOf("magnet:") == 0) {
			debug_log("Detected link as magnet");
			event.stopPropagation();
			event.preventDefault();
			debug_log("Captured magnet link "+url);
			chrome.runtime.sendMessage(
				{
					"method": "add_torrent_from_magnet",
					"url": url
				}
			);
			debug_log("Link sent to Deluge.")
		}
	}
	if (ExtensionConfig.handle_torrents) {
		debug_log("Handling torrents enabled");
		if (url.indexOf(".torrent") > -1) {
			debug_log("Detected link as a torrent");
			event.stopPropagation();
			event.preventDefault();
			debug_log("Captured .torrent link "+url)
			chrome.runtime.sendMessage(
				{
					"method": "add_torrent_from_url",
					"url": url
				}
			);
			debug_log("Link sent to Deluge.")
		}
	}
});

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
	debug_log(request.msg);
});

debug_log("Creating click handler");
$("body").on("click", "a", function(event) {
	debug_log("Click handler activated.")
	var url = $(this).prop("href");
	debug_log("URL: "+url)
	if (ExtensionConfig.handle_magnets) {
		debug_log("Handling magnets enabled");
		if (url.indexOf("magnet:") == 0) {
			debug_log("Detected link as magnet");
			event.stopPropagation();
			event.preventDefault();
			debug_log("Captured magnet link "+url);
			chrome.runtime.sendMessage(
				{
					"method": "add_torrent_from_magnet",
					"url": url
				}
			);
			debug_log("Link sent to Deluge.")
		}
	}
	if (ExtensionConfig.handle_torrents) {
		debug_log("Handling torrents enabled");
		if (url.search(/\.torrent$/) > 0) {
			debug_log("Detected link as a torrent");
			event.stopPropagation();
			event.preventDefault();
			debug_log("Captured .torrent link "+url)
			chrome.runtime.sendMessage(
				{
					"method": "add_torrent_from_url",
					"url": url
				}
			);
			debug_log("Link sent to Deluge.")
		}
	}
});

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
	debug_log(request.msg);
});

Fix link click handling - some regular links were erroneously caught


sap.ui.define(["sap/ui/integration/Extension", "sap/ui/integration/ActionDefinition"], function (Extension, ActionDefinition) {
	"use strict";

	var CustomActionsExtension = Extension.extend("card.explorer.extension.customActions.CustomActionsExtension");

	CustomActionsExtension.prototype.onCardReady = function () {
		var oCard = this.getCard();

		oCard.addActionDefinition(new ActionDefinition({
			type: "Custom",
			text: "{i18n>reportActionText}",
			enabled: true,
			press: function (oEvent) {
				var oReportAction = oEvent.getSource();
				oReportAction.setEnabled(false);
			}
		}));

		// Actions can be added at any time, for example after response from a backend
		oCard.request({
				url: sap.ui.require.toUrl("card/explorer/extension/customActions/urlInfo.json")
			}).then(function (oRes) {
				oCard.addActionDefinition(new ActionDefinition({
					type: "Navigation",
					text: "{i18n>bookActionText}",
					icon: "sap-icon://learning-assistant",
					parameters: {
						url: oRes.url
					}
				}));
			});
	};

	return CustomActionsExtension;
});

sap.ui.define(["sap/ui/integration/Extension", "sap/ui/integration/ActionDefinition"], function (Extension, ActionDefinition) {
	"use strict";

	var CustomActionsExtension = Extension.extend("card.explorer.extension.customActions.CustomActionsExtension");

	CustomActionsExtension.prototype.onCardReady = function () {
		var oCard = this.getCard();

		oCard.addActionDefinition(new ActionDefinition({
			type: "Custom",
			text: "{i18n>reportActionText}",
			enabled: true,
			press: function (oEvent) {
				var oReportAction = oEvent.getSource();
				oReportAction.setEnabled(false);
			}
		}));

		// Actions can be added at any time, for example after response from a backend
		oCard.request({
				url: "./urlInfo.json"
			}).then(function (oRes) {
				oCard.addActionDefinition(new ActionDefinition({
					type: "Navigation",
					text: "{i18n>bookActionText}",
					icon: "sap-icon://learning-assistant",
					parameters: {
						url: oRes.url
					}
				}));
			});
	};

	return CustomActionsExtension;
});

Use correct path in Custom Actions sample


// ----------------------------------------------------------------
// Login Class

class LoginModel extends SwitchModel {
  constructor({
    name,
    lsKeyView,
    triggerSelector,
    switchSelector
  } = {}) {
    super({
      name: name,
      lsKeyView: lsKeyView,
      triggerSelector: triggerSelector,
      switchSelector: switchSelector
    });
  }
}

class LoginView extends SwitchView {
  constructor(_model = new LoginModel()) {
    super(_model);
  }
}

// ----------------------------------------------------------------
// Controller

class LoginController extends CommonController {
  constructor(_obj) {
    super(_obj);

    this.model = new LoginModel(_obj);
    this.view = new LoginView(this.model);
  }
}

// ----------------------------------------------------------------
// Event

class LoginEvent extends CommonEvent {
  constructor({
    name = 'Login Event'
  } = {})
  {
    super({
      name: name
    });

    this.NAME = name;
    this.CONTROLLER = new LoginController({
      name: 'Login Switch',
      lsKeyView: 'login',
      triggerSelector: '#action-login',
      switchSelector: '#login-area'
    });
  }

}

// ----------------------------------------------------------------
// Login Class

class LoginModel extends SwitchModel {
  constructor({
    name,
    lsKeyView,
    triggerSelector,
    switchSelector
  } = {}) {
    super({
      name: name,
      lsKeyView: lsKeyView,
      triggerSelector: triggerSelector,
      switchSelector: switchSelector
    });

    this.LOGIN_AREA_SELECTOR = '#login-area';
    this.$LOGIN_AREA_SELECTOR = $(this.LOGIN_AREA_SELECTOR);
    this.TEMPLATE_LOGINED_SELECTOR = '#logined-template';
    this.$TEMPLATE_LOGINED_SELECTOR = $(this.TEMPLATE_LOGINED_SELECTOR);
    this.TEMPLATE_NOT_LOGIN_SELECTOR = '#not-login-template';
    this.$TEMPLATE_NOT_LOGIN_SELECTOR = $(this.TEMPLATE_NOT_LOGIN_SELECTOR);
  }
}

class LoginView extends SwitchView {
  constructor(_model = new LoginModel()) {
    super(_model);
  }
}

// ----------------------------------------------------------------
// Controller

class LoginController extends CommonController {
  constructor(_obj) {
    super(_obj);

    this.model = new LoginModel(_obj);
    this.view = new LoginView(this.model);
  }
}

// ----------------------------------------------------------------
// Event

class LoginEvent extends CommonEvent {
  constructor({
    name = 'Login Event'
  } = {})
  {
    super({
      name: name
    });

    this.NAME = name;
    this.CONTROLLER = new LoginController({
      name: 'Login Switch',
      lsKeyView: 'login',
      triggerSelector: '#action-login',
      switchSelector: '#login-area'
    });
  }

}

Add some template selectorin LoginModel


/* global $, document */
$(function() {
  $(document).on('click', '#add_membership', function(e) {
    e.preventDefault();
    $.ajax({
      url: this,
      success: function(data) {
        var el_to_add;
        el_to_add = $(data).html();
        $('#memberships').append(el_to_add);
      }
    });
  });
});

$(function() {
  $(document).on('click', 'a.remove-new-membership', function(e) {
    e.preventDefault();
    $(this).parents('.membership').remove();
  });
});

$(function() {
  $(document).on('click', 'a.show-role-editable', function(e) {
     e.preventDefault();
     $(this).closest('.role-summary').hide();
     $(this).closest('.membership').children('.editable-fields').show();
   });
});

Add trailing semicolon to js file


module.exports = function(grunt) {
	'use strict';
	return {
		isotope: {
			options: {
				patcher: function(text) {
					var re = grunt.config('env.name') === 'development'
						? /window.jQuery/g
						: /\w{1,2}\.jQuery/g;
					return text.replace(re, 'Echo.jQuery');
				}
			},
			files: [{
				src: ['<%= dirs.build %>/third-party/isotope.pkgd.js']
			}]
		}
	};
};

module.exports = function(grunt) {
	'use strict';
	return {
		isotope: {
			options: {
				patcher: function(text) {
					var jqueryRe = grunt.config('env.name') === 'development'
						? /window.jQuery/g
						: /\w{1,2}\.jQuery/g;
					var defineRe = grunt.config('env.name') === 'development'
						? /typeof define === 'function' \&\& define\.amd/g
						: /"function"==typeof define&&define.amd/g;
					return text
						.replace(jqueryRe, 'Echo.jQuery')
						.replace(defineRe, 'false');
				}
			},
			files: [{
				src: ['<%= dirs.build %>/third-party/isotope.pkgd.js']
			}]
		}
	};
};

Fix issue with isotope and define method


/* eslint-env jest */

import React from 'react'
import {shallow} from 'enzyme'
import Ad from '../Ad'

jest.mock('ads/AdClient')
jest.mock('ads/activeAdClient')

describe('Ad component', function () {
  it('should render without throwing an error', function () {
    const wrapper = shallow(
      <Ad
        adId='abc123'
        adSlotId='def'
        width={300}
        height={250} />
    )
    expect(wrapper.contains(<div id='abc123' />)).toBe(true)
  })
})

/* eslint-env jest */

import React from 'react'
import { shallow } from 'enzyme'
import Ad from '../Ad'
import AdClient from 'ads/AdClient'

jest.mock('ads/AdClient')
jest.mock('ads/activeAdClient')

describe('Ad component', function () {
  it('should render without throwing an error', function () {
    const wrapper = shallow(
      <Ad
        adId='abc123'
        adSlotId='def'
        width={300}
        height={250} />
    )
    expect(wrapper.contains(<div id='abc123' />)).toBe(true)
  })

  it('calls to display ad on mount', function () {
    const wrapper = shallow(
      <Ad
        adId='abc123'
        adSlotId='def'
        width={300}
        height={250} />
    )
    expect(AdClient.displayAd).not.toHaveBeenCalled()
    wrapper.instance().componentDidMount()
    expect(AdClient.displayAd).toHaveBeenCalledTimes(1)
  })
})

Test ad component call to display an ad.


import { updateQuery, fetchQuery, addSearch, isSearching } from '../actions';
import { search } from '../actions/SearchAPI';
import Icon from './Icon';

export default class SearchForm extends React.Component {
  constructor() {
    super()
    this._debouncedSearch = _.debounce(this._debouncedSearch, 300)
  }

  render() {
    return (
      <form onSubmit={e => e.preventDefault()}>

          <input onChange={this._handleKeyUp.bind(this)}
               id='Search'
               type='text'
               autoComplete='off'
               placeholder='Search Wikipedia'
               defaultValue={this.props.Search.queries[this.props.index]}
               className='field border-bottom search__input'/>

<Icon size="18px" icon="search" fill={'silver'} />

      </form>
    );
  }

  _handleKeyUp(e) {
    e.persist()
    this._debouncedSearch(e.target.value.trim())
  }

  _debouncedSearch(query) {
    const {dispatch, index} = this.props;
    dispatch(updateQuery(index, query))
    dispatch(isSearching(true))
    if (query) {
      search(query, this._handleResults.bind(this));
    } else {
      this._handleResults([])
    }
  }
  _handleResults(results) {
    this.props.dispatch(addSearch(results, this.props.index))
    this.props.dispatch(isSearching(false))
  }
}

import { updateQuery, fetchQuery, addSearch, isSearching } from '../actions';
import { search } from '../actions/SearchAPI';
import Icon from './Icon';

export default class SearchForm extends React.Component {
  constructor() {
    super()
    this._debouncedSearch = _.debounce(this._debouncedSearch, 300)
  }

  render() {
    return (
      <form onSubmit={e => e.preventDefault()}>

          <input onChange={this._handleInputChange.bind(this)}
               id='Search'
               type='text'
               autoComplete='off'
               placeholder='Search Wikipedia'
               defaultValue={this.props.Search.queries[this.props.index]}
               className='field border-bottom search__input'/>

<Icon size="18px" icon="search" fill={'silver'} />

      </form>
    );
  }

  _handleInputChange(e) {
    e.persist()
    this._debouncedSearch(e.target.value.trim())
  }

  _debouncedSearch(query) {
    const {dispatch, index} = this.props;
    dispatch(updateQuery(index, query))
    dispatch(isSearching(true))
    if (query) {
      search(query, this._handleResults.bind(this));
    } else {
      this._handleResults([])
    }
  }
  _handleResults(results) {
    this.props.dispatch(addSearch(results, this.props.index))
    this.props.dispatch(isSearching(false))
  }
}

Improve method name for search input change


(function(){

    angular
        .module('openeApp')
        .controller('FooterController', FooterController);

    FooterController.$inject = ['$scope', 'authService', 'serverVersionService'];

    function FooterController($scope, authService, serverVersionService) {
        var vm = this;

        activate();
        debugger;

        function activate() {
            vm.isDevelopmentMode = document.location.hostname == "localhost" ||
                document.location.hostname == "test.openesdh.dk";

            serverVersionService.getGitDetails().then(function (details) {
                vm.gitCommitId = details.gitCommitId;
                vm.gitBranch = details.gitBranch;
            });
        }
    }
})();

(function(){

    angular
        .module('openeApp')
        .controller('FooterController', FooterController);

    FooterController.$inject = ['$scope', 'authService', 'serverVersionService'];

    function FooterController($scope, authService, serverVersionService) {
        var vm = this;

        activate();

        function activate() {
            vm.isDevelopmentMode = document.location.hostname == "localhost" ||
                document.location.hostname == "test.openesdh.dk";

            serverVersionService.getGitDetails().then(function (details) {
                vm.gitCommitId = details.gitCommitId;
                vm.gitBranch = details.gitBranch;
            });
        }
    }
})();

Remove debugger statement from Footer.js


import localDB from './localDb';

console.log(localDB.getItemById(2323));

// ACTIONS
const FILTER = 'FILTER';

// REDUCER
const initialState = {
};

const dashboard = (state = initialState, action) => {
    switch (action.type) {
        case FILTER:
            return {
                ...state,
                filter: action.filter
            };
        default:
            return state;
    }
};

// DISPATCHERS
export function filterTable(filterVal) {
    return {
        type: FILTER,
        filter: filterVal
    };
}

export default dashboard;

import localDB from './localDb';

// ACTIONS
const FILTER = 'FILTER';
const FETCH_ITEM = 'FETCH_ITEM';

// REDUCER
const initialState = {
};

const dashboard = (state = initialState, action) => {
    switch (action.type) {
        case FILTER:
            return {
                ...state,
                filter: action.filter
            };

        case FETCH_ITEM:
            return {
                ...state,
                result: action.result
            };
        default:
            return state;
    }
};

// DISPATCHERS
export function filterTable(filterVal) {
    return {
        type: FILTER,
        filter: filterVal
    };
}

export function getItemsByUrl(url) {
    // add middleware later to support mapping with promise and actions
    const result = localDB.getItemByUrl(url);
    console.log('............RESULT.............');
    console.log(result);

    return {
        type: FETCH_ITEM,
        result
    };
}

export default dashboard;

Add fetch by url api in reducer.


'use babel'

import * as util from './util'
import { Range } from 'atom'

export default {
  wordRegExp: /([\w\.\-_]+)/,
  providerName: "moie-modelica",
  setClient(client) {
    this.client = client
    console.log("setting client in hyperclickProvider")
  },

  getSuggestionForWord(editor, text, range) {
    self = this
    return new Promise((resolve, reject) => {
  	  if(util.isModelicaFile(editor.getPath()) && text.trim() !== "") {
        const className = text
        self.client.getContainingFile(
          className,
          (data) => {
            resolve({
              range:range,
              callback: () => util.openFile(data.path)
            })
          },
          (xhr, status, error) => {
            resolve({
              range: range,
              callback: () => atom.notifications.addWarning("Can't go to anywhere :-(", {detail:`can't find source for ${className}`})
            })
          })
      }
    })
  }
}

'use babel'

import * as util from './util'
import { Range } from 'atom'

export default {
  wordRegExp: /([\w\.\-_]+)/,
  providerName: "moie-modelica",
  setClient(client) {
    this.client = client
    console.log("setting client in hyperclickProvider")
  },

  getSuggestionForWord(editor, text, range) {
    self = this
    return new Promise((resolve, reject) => {
  	  if(util.isModelicaFile(editor.getPath()) && text.trim() !== "") {
        const className = text
        self.client.getContainingFile(
          className,
          (data) => {
            resolve({
              range:range,
              callback: () => atom.workspace.open(data.path, {initialLine: data.line})
            })
          },
          (xhr, status, error) => {
            resolve({
              range: range,
              callback: () => atom.notifications.addWarning("Can't go to anywhere :-(", {detail:`can't find source for ${className}`})
            })
          })
      }
    })
  }
}

Set initialLine of found file


/* globals module */
module.exports = {
  normalizeEntityName: function() {},

  afterInstall: function() {
    return this.addBowerPackagesToProject([
      { name: 'd3', target: '~3.5.3' },
      { name: 'rx-ember', target: '~0.2.3'},
      { name: 'rxjs', target: '~2.4.6'}
    ]);
  }
};

/* globals module */
module.exports = {
  normalizeEntityName: function() {},

  afterInstall: function() {
    return this.addBowerPackagesToProject([
      { name: 'd3', target: '~3.5.3' },
      { name: 'rx-ember', target: '~0.2.5-1'},
      { name: 'rxjs', target: '~2.4.6'}
    ]);
  }
};

Correct bower rx-ember tag in addon blueprint


/*
 * <%= props.name %>
 * <%= props.homepage %>
 *
 * Copyright (c) <%= currentYear %><% if (props.authorName) { %> <%= props.authorName %><% } %>
 * Licensed under the <%= props.license %> license.
 */

'use strict';

// Following the 'Node.js require(s) best practices' by
// http://www.mircozeiss.com/node-js-require-s-best-practices/

// // Nodejs libs
// var fs = require('fs'),
//
// // External libs
// debug = require('debug'),
//
// // Internal libs
// data = require('./data.js');

var <%= safeSlugname %> = require('../');

<%= safeSlugname %>.awesome(); // "awesome"

/*
 * <%= props.name %>
 * <%= props.homepage %>
 *
 * Copyright (c) <%= currentYear %><% if (props.authorName) { %> <%= props.authorName %><% } %>
 * Licensed under the <%= props.license %> license.
 */

'use strict';

// Following the 'Node.js require(s) best practices' by
// http://www.mircozeiss.com/node-js-require-s-best-practices/

// // Nodejs libs
// var fs = require('fs'),
//
// // External libs
// debug = require('debug'),
//
// // Internal libs
// data = require('./data.js');

var <%= safeSlugname %> = require('../');

console.log(<%= safeSlugname %>.awesome()); // "awesome"

Add console.log for return value in example


// ==UserScript==
// @name         Claro Video - No mouse hijacking
// @namespace    http://federicojm.com/
// @version      0.1
// @description  Disable mouse hijacking by Claro Video
// @author       Federico JM
// @match        https://www.clarovideo.com/playerhtml5/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

document.onselectstart = null
document.oncontextmenu = null
document.onmousedown = null

// ==UserScript==
// @name         Claro Video - No video hijacking
// @namespace    http://federicojm.com/
// @version      0.2
// @description  Disable video control hijacking by Claro Video
// @author       Federico JM
// @match        https://www.clarovideo.com/playerhtml5/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

;(() => {
  document.onselectstart = null
  document.oncontextmenu = null
  document.onmousedown = null

  let intervalId = window.setInterval(() => {
    let videoControls = document.querySelectorAll('vph5-controls')
    if (!videoControls.length) {
      return
    }
    window.clearInterval(intervalId)

    videoControls.forEach(node => {
      node.remove()
    })
    document.querySelector('video').controls = true
  }, 500)
})()

Use native video controls in Claro video


importScripts("/app/bower_components/videogular-questions/questions-worker.js");

loadAnnotations({
	"first-question": {
		time: 8,
		questions: [
			{
				id: "first-question",
				type: "single",
				question: "What is the moon made of?",
				options: [
					{
						name: "cheese"
					},
					{
						name: "cheeese"
					},
					{
						name: "cheeeeeese"
					}
				]
			},
			{
				id: "check-question",
				type: "single",
				question: "Answer incorrect, do you want to review the video",
				options: [
					{
						name: "Yes",
						action: function(video) {
							video.setTime(0);
						}
					},
					{
						name: "No"
					}
				],
				condition: function(questions) {
					// show if the answer to the previous question is not "cheese"
					return questions[0].answer !== "cheese";
				}
			}
		]
	}
});

importScripts("/app/bower_components/videogular-questions/questions-worker.js");

loadAnnotations({
	"first-question": {
		time: 4,
		questions: [
			{
				id: "first-question",
				type: "single",
				question: "What is the moon made of?",
				options: [
					{
						name: "cheese"
					},
					{
						name: "cheeese"
					},
					{
						name: "cheeeeeese"
					}
				]
			},
			{
				id: "check-question",
				type: "single",
				question: "Answer incorrect, do you want to review the video",
				options: [
					{
						name: "Yes",
						action: function(video) {
							video.setTime(0);
						}
					},
					{
						name: "No"
					}
				],
				condition: function(questions, result) {
					// show if the answer to the previous question is not "cheese"
					return result !== "cheese";
				}
			}
		]
	}
});

Add questions having access to results


/*
 * simple-sudoku-check
 * https://github.com/jbranchaud/simple-sudoku-check
 *
 * Copyright (c) 2014 Josh Branchaud
 * Licensed under the MIT license.
 */

'use strict';

exports.awesome = function() {
  return 'awesome';
};

/*
 * simple-sudoku-check
 * https://github.com/jbranchaud/simple-sudoku-check
 *
 * Copyright (c) 2014 Josh Branchaud
 * Licensed under the MIT license.
 */

'use strict';

module.exports = {
  awesome: function() {
    return 'awesome';
  }
};

Update the way the main JavaScript file is organized.


var Fluxy = require('fluxy')
var Promise = require('bluebird')
var uuid = require('node-uuid')
var _ = require('lodash')
var mori = require('mori')

var StudyStore = require('../stores/StudyStore')
var ScheduleStore = require('../stores/ScheduleStore')

var StudentService = {
	create: function(student) {
		return new Promise(function(resolve, reject) {
			var s = {
				id: student.id || uuid.v4(),
				name: student.name || '',
				active: student.active || false,
				enrollment: student.enrollment || 0,
				graduation: student.graduation || 0,
				creditsNeeded: student.creditsNeeded || 0,
				studies: student.studies.length > 0 ? mori.vector(student.studies) : mori.vector(),
				schedules: student.schedules.length > 0 ? mori.vector(student.schedules) : mori.vector(),
				// overrides: new OverrideStore(),
				// fabrications: new FabricationStore(),
			}
			console.log('called StudentService.create', s)
			resolve(mori.js_to_clj(s))
		})
	},
	encode: function(student) {

	},
	decode: function(encodedStudent) {
		// decode stuff
		var decodedStudent = encodedStudent
		this.create(decodedStudent)
	}
}

module.exports = StudentService

var Fluxy = require('fluxy')
var Promise = require('bluebird')
var uuid = require('node-uuid')
var _ = require('lodash')
var mori = require('mori')

var StudyStore = require('../stores/StudyStore')
var ScheduleStore = require('../stores/ScheduleStore')

var StudentService = {
	create: function(student) {
		return new Promise(function(resolve, reject) {
			var s = {
				id: student.id || uuid.v4(),
				name: student.name || '',
				active: student.active || false,
				enrollment: student.enrollment || 0,
				graduation: student.graduation || 0,
				creditsNeeded: student.creditsNeeded || 0,
				studies: student.studies.length > 0 ? mori.hash_map(student.studies) : mori.hash_map(),
				schedules: student.schedules.length > 0 ? mori.hash_map(student.schedules) : mori.hash_map(),
				// overrides: new OverrideStore(),
				// fabrications: new FabricationStore(),
			}
			console.log('called StudentService.create', s)
			resolve(mori.js_to_clj(s))
		})
	},
	encode: function(student) {

	},
	decode: function(encodedStudent) {
		// decode stuff
		var decodedStudent = encodedStudent
		this.create(decodedStudent)
	}
}

module.exports = StudentService

Use hash_maps for student.studies and student.schedules


/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
'use strict';

/**
 * Manually resolve all default Babel plugins.
 * `babel.transform` will attempt to resolve all base plugins relative to
 * the file it's compiling. This makes sure that we're using the plugins
 * installed in the react-native package.
 */
function resolvePlugins(plugins) {
  return plugins.map(resolvePlugin);
}

/**
 * Manually resolve a single Babel plugin.
 */
function resolvePlugin(plugin) {
  // Normalise plugin to an array.
  if (!Array.isArray(plugin)) {
    plugin = [plugin];
  }
  // Only resolve the plugin if it's a string reference.
  if (typeof plugin[0] === 'string') {
    plugin[0] = require('babel-plugin-' + plugin[0]);
    plugin[0] = plugin[0].__esModule ? plugin[0].default : plugin[0];
  }
  return plugin;
}

module.exports = resolvePlugins;
module.exports.resolvePlugin = resolvePlugin;

/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 */
'use strict';

/**
 * Manually resolve all default Babel plugins.
 * `babel.transform` will attempt to resolve all base plugins relative to
 * the file it's compiling. This makes sure that we're using the plugins
 * installed in the react-native package.
 */
function resolvePlugins(plugins, prefix) {
  return plugins.map(plugin => resolvePlugin(plugin, prefix));
}

/**
 * Manually resolve a single Babel plugin.
 */
function resolvePlugin(plugin, prefix = 'babel-plugin-') {
  // Normalise plugin to an array.
  if (!Array.isArray(plugin)) {
    plugin = [plugin];
  }
  // Only resolve the plugin if it's a string reference.
  if (typeof plugin[0] === 'string') {
    plugin[0] = require(prefix + plugin[0]);
    plugin[0] = plugin[0].__esModule ? plugin[0].default : plugin[0];
  }
  return plugin;
}

module.exports = resolvePlugins;
module.exports.resolvePlugin = resolvePlugin;
module.exports.resolvePluginAs = (prefix, plugin) =>
  resolvePlugin(plugin, prefix);
module.exports.resolvePluginsAs = (prefix, plugins) =>
  resolvePlugins(plugins, prefix);

Add possibility to add custom plugin prefix


import Vue from 'vue';
import vuexI18n from 'vuex-i18n';

import store from '../store';
import locales from './locales';

import fallback from '../assets/i18n/en.json';

Vue.use(vuexI18n.plugin, store);

Vue.i18n.add('en', fallback);
Vue.i18n.set('en');
Vue.i18n.fallback('en');

export default {
    init() {
        let userLang = window.localStorage.getItem('contao_manager_locale');

        if (!userLang) {
            userLang = navigator.language || navigator.userLanguage;
        }

        return this.load(userLang);
    },

    load(locale) {
        window.localStorage.setItem('contao_manager_locale', locale);

        if (Vue.i18n.exists(locale)) {
            Vue.i18n.set(locale);
            return new Promise(resolve => resolve());
        }

        if (!locales[locale]) {
            if (locale.length === 5) {
                return this.load(locale.slice(0, 2));
            }

            return new Promise(resolve => resolve());
        }

        return Vue.http.get(`assets/i18n/${locale}.json`).then(
            response => response.json().then((json) => {
                Vue.i18n.add(locale, json);
                Vue.i18n.set(locale);
            }),
            () => {},
        );
    },
};

import Vue from 'vue';
import vuexI18n from 'vuex-i18n';

import store from '../store';
import locales from './locales';

import fallback from '../assets/i18n/en.json';

Vue.use(vuexI18n.plugin, store);

Vue.i18n.add('en', fallback);
Vue.i18n.set('en');
Vue.i18n.fallback('en');

export default {
    init() {
        let userLang = window.localStorage.getItem('contao_manager_locale');

        if (!userLang) {
            userLang = navigator.language || navigator.userLanguage;
        }

        return this.load(userLang);
    },

    load(locale) {
        window.localStorage.setItem('contao_manager_locale', locale);

        if (Vue.i18n.localeExists(locale)) {
            Vue.i18n.set(locale);
            return new Promise(resolve => resolve());
        }

        if (!locales[locale]) {
            if (locale.length === 5) {
                return this.load(locale.slice(0, 2));
            }

            return new Promise(resolve => resolve());
        }

        return Vue.http.get(`assets/i18n/${locale}.json`).then(
            response => response.json().then((json) => {
                Vue.i18n.add(locale, json);
                Vue.i18n.set(locale);
            }),
            () => {},
        );
    },
};

Update to latest i18n API


import { extend } from 'flarum/extend';
import app from 'flarum/app';
import PermissionGrid from 'flarum/components/PermissionGrid';

app.initializers.add('flarum-sticky', () => {
  extend(PermissionGrid.prototype, 'moderateItems', items => {
    items.add('sticky', {
      icon: 'fas fa-thumbtack',
      label: app.translator.trans('flarum-sticky.admin.permissions.sticky_discussions_label'),
      permission: 'discussion.sticky'
    }, 95);
  });
});

import app from 'flarum/app';

app.initializers.add('flarum-sticky', () => {
  app.extensionData
    .for('flarum-sticky')
    .registerPermission({
      icon: 'fas fa-thumbtack',
      label: app.translator.trans('flarum-sticky.admin.permissions.sticky_discussions_label'),
      permission: 'discussion.sticky'
    }, 'moderate', 95);
});

Update admin js for admin ux redesign


angular.module('moped.widgets', [
  'moped.util'
])
.directive('mopedTrack', function(util) {
  return {
    restrict: 'EA',
    scope: {
      trackNo: '=',
      track: '='
    },
    replace: true,
    link: function(scope, element, attrs) {
      scope.artistsAsString = function() {
        return util.getTrackArtistsAsString(scope.track);
      };
      scope.trackDuration = function() {
        return util.getTrackDuration(scope.track);
      };
      scope.playTrack = function() {
        scope.$emit('moped:playtrackrequest', scope.track);
        return false;
      };
      scope.$on('mopidy:event:trackPlaybackStarted', function(event, data) {
        scope.isPlaying = data.tl_track.track.uri === scope.track.uri;
        scope.$apply();
      });

      scope.$on('mopidy:event:trackPlaybackPaused', function(event, data) {
        scope.isPlaying = data.tl_track.track.uri === scope.track.uri;
        scope.$apply();
      });

      scope.$on('moped:currenttrackrequested', function(track) {
        scope.isPlaying = track.uri === scope.track.uri;
        scope.$apply();
      });
    },
    controller : function ($scope) {

    },
    templateUrl: 'widgets/track.tpl.html'
  };
});

angular.module('moped.widgets', [
  'moped.util'
])
.directive('mopedTrack', function(util) {
  return {
    restrict: 'EA',
    scope: {
      trackNo: '=',
      track: '='
    },
    replace: true,
    templateUrl: 'widgets/track.tpl.html',
    link: function(scope, element, attrs) {
      scope.artistsAsString = function() {
        return util.getTrackArtistsAsString(scope.track);
      };
      scope.trackDuration = function() {
        return util.getTrackDuration(scope.track);
      };
      scope.playTrack = function() {
        scope.$emit('moped:playtrackrequest', scope.track);
        return false;
      };
      var cleanUpTrackPlaybackStarted = scope.$on('mopidy:event:trackPlaybackStarted', function(event, data) {
        scope.isPlaying = data.tl_track.track.uri === scope.track.uri;
        scope.$apply();
      });

      var cleanUpTrackPlaybackPaused = scope.$on('mopidy:event:trackPlaybackPaused', function(event, data) {
        scope.isPlaying = data.tl_track.track.uri === scope.track.uri;
        scope.$apply();
      });

      var cleanUpCurrentTrackRequested = scope.$on('moped:currenttrackrequested', function(track) {
        scope.isPlaying = track.uri === scope.track.uri;
        scope.$apply();
      });

      $scope.$on('$destroy', function() {
        cleanUpTrackPlaybackStarted();
        cleanUpTrackPlaybackPaused();
        cleanUpCurrentTrackRequested();
      };
    }
  };
});

Set page title via route property.


define(['mac/roman'], function(macintoshRoman) {

  'use strict';

  return function(item) {
    return item.getBytes().then(function(bytes) {
      var text = macintoshRoman(bytes, 0, bytes.length);
      debugger;
    });
  };

});

define(['mac/roman'], function(macintoshRoman) {

  'use strict';

  function open(item) {
    return item.getBytes().then(function(bytes) {
      item.setDataObject(new TextView(bytes.buffer, bytes.byteOffset, bytes.byteLength));
    });
  };

  function TextView(buffer, byteOffset, byteLength) {
    this.dataView = new DataView(buffer, byteOffset, byteLength);
  };
  TextView.prototype = {
    toJSON: function() {
      return {
        top: this.top,
        left: this.left,
        bottom: this.bottom,
        right: this.right,
        fontType: this.fontType,
        fontSize: this.fontSize,
        text: this.text,
      };
    },
    get top() {
      return this.dataView.getInt16(0, false);
    },
    get left() {
      return this.dataView.getInt16(2, false);
    },
    get bottom() {
      return this.dataView.getInt16(4, false);
    },
    get right() {
      return this.dataView.getInt16(6, false);
    },
    get fontType() {
      return this.dataView.getUint16(8, false);
    },
    get fontSize() {
      return this.dataView.getUint16(10, false);
    },
    get text() {
      return macRoman(this.bytes, 12);
    },
  };

  return open;

});

Add text window data fields


import {
  define,
  props,
  withComponent,
  css,
  hasNativeShadowDomSupport,
  BoltComponent,
  sanitizeBoltClasses,
} from '@bolt/core';

import ClipboardJS from 'clipboard';

@define
export class BoltCopyToClipboard extends BoltComponent() {
  static is = 'bolt-copy-to-clipboard';

  constructor(self) {
    self = super(self);
    this.useShadow = false;
    return self;
  }

  clickHandler(event) {
    event.preventDefault(); // Prevent the default link behavior
  }

  connecting() {
    this.link = this.querySelector('a[href]');
    this.copyTrigger = this.querySelector('[data-clipboard-text]');
    this.parentElem = this.querySelector('.js-bolt-copy-to-clipboard');

    // Disable a link that appears within the trigger.
    this.link.addEventListener('click', this.clickHandler);

    this.clipboardInstance = new ClipboardJS(this.copyTrigger);

    this.clipboardInstance.on('success', () => {
      // Copying is already successful at this point.  Everything from here on is UX flair.

      // Show the "in progress" status.
      this.parentElem.classList.add('is-animating');

      // Show the "success" status.
      setTimeout(() => {
        this.parentElem.classList.add('is-successful');

        // Reset so the link can be used again without refreshing the page.
        setTimeout(() => {
          this.parentElem.classList.remove('is-successful');
          this.parentElem.classList.remove('is-animating');
        }, 3000);
      }, 1000);
    });
  }

  disconnecting() {
    this.clipboardInstance.destroy();
    this.copyLink.removeEventListener('click', this.clickHandler);
  }
}

import {
  define,
  props,
  withComponent,
  css,
  hasNativeShadowDomSupport,
  BoltComponent,
  sanitizeBoltClasses,
} from '@bolt/core';

import ClipboardJS from 'clipboard';

@define
export class BoltCopyToClipboard extends BoltComponent() {
  static is = 'bolt-copy-to-clipboard';

  constructor(self) {
    self = super(self);
    this.useShadow = false;
    return self;
  }

  connecting() {
    this.copyTrigger = this.querySelector('[data-clipboard-text]');
    this.parentElem = this.querySelector('.js-bolt-copy-to-clipboard');

    this.clipboardInstance = new ClipboardJS(this.copyTrigger);

    this.clipboardInstance.on('success', () => {
      // Copying is already successful at this point.  Everything from here on is UX flair.

      // Show the "in progress" status.
      this.parentElem.classList.add('is-animating');

      // Show the "success" status.
      setTimeout(() => {
        this.parentElem.classList.add('is-successful');

        // Reset so the link can be used again without refreshing the page.
        setTimeout(() => {
          this.parentElem.classList.remove('is-successful');
          this.parentElem.classList.remove('is-animating');
        }, 3000);
      }, 1000);
    });
  }

  disconnecting() {
    this.clipboardInstance.destroy();
  }
}

Remove js that disables links to prepare for using button instead


import React, { Component } from 'react';
import Box from 'grommet/components/Box';
import { SectionHeader } from 'components';

export default class AboutUsPage extends Component {
  static fetchData() {
  }
  render() {
    return (
      <Box justify="center" align="center">
        <SectionHeader title="About Us" />
        <Box size={{ width: 'xlarge' }} align="center" pad="medium">

        </Box>
      </Box>
    );
  }
}

import React, { Component } from 'react';
import Box from 'grommet/components/Box';
import Paragraph from 'grommet/components/Paragraph';
import { SectionHeader } from 'components';

export default class AboutUsPage extends Component {
  static fetchData() {
  }
  render() {
    return (
      <Box
        justify="center"
        align="center"
        alignSelf="center"
      >
        <Box direction="row" pad={{ between: 'medium' }}>
          <Box align="center" size={{ width: 'medium' }}>
            <SectionHeader title="Hospital Hours" />
            <Box align="center" pad="medium">
              <Paragraph margin="small">
                Monday - Friday: 7:30 am - 7:30 pm
              </Paragraph>
              <Paragraph margin="small">
                Saturday: 7:30 am - 1:30 pm
              </Paragraph>
              <Paragraph margin="small">
                Appointments Preferred
              </Paragraph>
            </Box>
          </Box>
          <Box align="center" size={{ width: 'medium' }}>
            <SectionHeader title="Contact" />
            <Box align="center" pad="medium">
              <Paragraph margin="small">
                1221 Belle Haven Rd, Alexandria, VA 22307
              </Paragraph>
              <Paragraph margin="small">
                (703) 721 - 0088
              </Paragraph>
              <Paragraph margin="small">
                info@bhamc.com
              </Paragraph>
            </Box>
          </Box>
        </Box>
        <Box align="center" size={{ width: 'xlarge' }}>
          <SectionHeader title="Contact" />
          <Box align="center" pad="medium" full="horizontal">
            <iframe
              width="100%"
              height="400"
              frameBorder="0"
              scrolling="no"
              marginHeight="0"
              marginWidth="0"
              src="http://maps.google.com/maps?q=1221+Belle+Haven+Road+Alexandria,+VA+22307&oe=utf-8&client=firefox-a&ie=UTF8&hq=&hnear=1221+Belle+Haven+Rd,+Alexandria,+Virginia+22307&gl=us&ll=38.785937,-77.057076&spn=0.047636,0.103683&z=14&output=embed&iwloc=near&addr&"
            />
          </Box>
        </Box>
      </Box>
    );
  }
}

Add content to About Us page.


var pageMod = require('page-mod'),
    data = require('self').data,
    file = require('file'),
    url = require('url');

pageMod.PageMod({
    include: "*",
    contentScriptWhen: 'ready',
    contentScriptFile: data.url('jquery-1.5.min.js'),
    contentScript:
        '(function($) {' +
            'onMessage = function onMessage(script) { eval(script); };' +
            'postMessage(document.URL);' +
        '}(jQuery.noConflict(true)));',
    onAttach: function onAttach(worker) {
        worker.on('message', function(data) {
            var domain = url.URL(data).host;
            if (domain.indexOf('www.') === 0) {
                domain = domain.substring(4, domain.length);
            }
            var filename = '~/.js/' + domain + '.js';
            if (file.exists(filename)) {
                worker.postMessage(file.read(filename));
            }
        });
    }
});

var pageMod = require('page-mod'),
    data = require('self').data,
    file = require('file'),
    url = require('url');

pageMod.PageMod({
    include: "*",
    contentScriptWhen: 'ready',
    contentScriptFile: data.url('jquery-1.5.min.js'),
    contentScript:
        '(function($) {' +
            'onMessage = function onMessage(script) { eval(script); };' +
            'postMessage(document.URL);' +
        '}(jQuery.noConflict(true)));',
    onAttach: function onAttach(worker) {
        worker.on('message', function(data) {
            var domain = url.URL(data).host;
            if (domain.indexOf('www.') === 0) {
                domain = domain.substring(4, domain.length);
            }
            var files = ['default', domain];
            for (var i=0; i < files.length; i++) {
                filename = '~/.js/' + files[i] + '.js';
                if (file.exists(filename)) {
                    worker.postMessage(file.read(filename));
                }
            }
        });
    }
});

Load default.js if it exists.


import Controller from '@ember/controller';

export default Controller.extend({
  isLoading: false,

  actions: {
    translationsDownload() {
      this.set('isLoading', true);
      this.get('loader')
        .downloadFile('/admin/content/translations/all')
        .then(() => {
          this.get('notify').success(this.get('l10n').t('Translations Zip generated successfully.'));
        })
        .catch(e => {
          console.warn(e);
          this.get('notify').error(this.get('l10n').t('Unexpected error occurred.'));
        })
        .finally(() => {
          this.set('isLoading', false);
        });
    }
  }
});

import Controller from '@ember/controller';

export default Controller.extend({
  isLoading: false,

  actions: {
    translationsDownload() {
      this.set('isLoading', true);
      this.get('loader')
        .downloadFile('/admin/content/translations/all')
        .then(res => {
          const anchor = document.createElement('a');
          anchor.style.display = 'none';
          anchor.href = `data:text/plain;charset=utf-8,${encodeURIComponent(res)}`;
          anchor.download = 'Translations.zip';
          anchor.click();
          this.get('notify').success(this.get('l10n').t('Translations Zip generated successfully.'));
        })
        .catch(e => {
          console.warn(e);
          this.get('notify').error(this.get('l10n').t('Unexpected error occurred.'));
        })
        .finally(() => {
          this.set('isLoading', false);
        });
    }
  }
});

Refactor translation downloads according to downloadFile service


var path        = require( 'path' )
  , fs          = require( 'fs' )
  , fileNames   = [ 'seedData' ]
  , packageJson = require( path.resolve( __dirname + '/../../' ) + '/package.json' )
  , seedData    = require( path.resolve( path.join( __dirname, '..', '..', 'schema', 'seedData.json' ) ) );

packageJson.bundledDependencies.forEach(function( moduleName ) {
    var moduleConfigPath = [ path.resolve( __dirname + '/../../modules' ), moduleName, 'schema', '' ].join( path.sep );

    fileNames.forEach(function( fileName ) {
        var filePath = moduleConfigPath + fileName + '.json';
        if ( fs.existsSync( filePath ) ) {
            var data = require( filePath );
            Object.keys( data ).forEach( function( key ) {
                if ( !!seedData[ key ] ) {
                    seedData[ key ] = seedData[ key ].concat( data[ key ] );
                } else {
                    seedData[ key ] = data[ key ];
                }
            })
        }
    });
});

module.exports = seedData;

var path        = require('path')
  , fs          = require('fs')
  , packageJson = require(path.resolve(__dirname + '/../../') + '/package.json')
  , seedData    = {};

function loadSeedDataFile(filePath) {
  var data = require(filePath);
  Object.keys(data).forEach(function(key) {
    if (!!seedData[key]) {
      seedData[key] = seedData[key].concat(data[key]);
    } else {
      seedData[key] = data[key];
    }
  });
}

packageJson.bundledDependencies.forEach(function(moduleName) {
  var filePath = [path.resolve(__dirname + '/../../modules'), moduleName, 'schema', 'seedData.json'].join(path.sep)
  if (fs.existsSync(filePath)) {
    loadSeedDataFile(filePath);
  }
});

loadSeedDataFile(path.resolve(path.join(__dirname, '..', '..', 'schema', 'seedData.json')));

module.exports = seedData;

Order of seedData now places the main schema json after all other modules plus refactoring


'use strict';

var $ = require('jquery');
var $osf = require('js/osfHelpers.js');
var accountSettings = require('js/accountSettings.js');

$(function() {
    var viewModel = new accountSettings.UserProfileViewModel();
    $osf.applyBindings(viewModel, '#connectedEmails');
    viewModel.init();

    $osf.applyBindings(
        new accountSettings.DeactivateAccountViewModel(),
        '#deactivateAccount'
    );

    $osf.applyBindings(
        new accountSettings.ExportAccountViewModel(),
        '#exportAccount'
    );
});

'use strict';

var $ = require('jquery');
var $osf = require('js/osfHelpers.js');
var accountSettings = require('js/accountSettings.js');

$(function() {
    var viewModel = new accountSettings.UserProfileViewModel();
    $osf.applyBindings(viewModel, '#connectedEmails');
    viewModel.init();

    $osf.applyBindings(
        new accountSettings.ChangePasswordViewModel(),
        '#changePassword'
    );

    $osf.applyBindings(
        new accountSettings.DeactivateAccountViewModel(),
        '#deactivateAccount'
    );

    $osf.applyBindings(
        new accountSettings.ExportAccountViewModel(),
        '#exportAccount'
    );
});

Apply bindings for new change password VM


module.exports = {
  plugins: {
    'autoprefixer': {},
    'postcss-import': {},
    'postcss-nested': {},
    'cssnano': {}
  },
  'local-plugins': true,
}

module.exports = {
  plugins: {
    'postcss-import': {},
    'postcss-nested': {},
    'autoprefixer': {},
    'cssnano': {}
  },
  'local-plugins': true,
}

Change order of PostCSS plugins


$('form').submit(function(event) {
	event.preventDefault();

	var movie = $('#movie'),
		id = $('#search-id').val();

	movie.html('');

	var request = $.getJSON('/movie/' + id + '/110', function(data) {
		var html = '';
		html += '';
		html += '
';
		html += '
' + data.title + '
';
		html += '
Genres: ';
		$.each(data.genres, function(index, value) {
			html += value;
			if (data.genres[index + 1]) {
				html += ' / ';
			}
		});
		html += '
';
		html += '
Rating: ' + data.vote_average + ' (' + data.vote_count + ' votes)
';
		html += '
Release date: ' + data.release_date + '
';
		html += '
' + data.overview + '
';
		html += '
';
		movie.html(html);
	});

	request.fail(function(jqXHR, textStatus) {
		movie.html('
Bad request!
');
	});
});

$('form').submit(function(event) {
	event.preventDefault();

	var movie = $('#movie'),
		id = $('#search-id').val();

	movie.html('');

	var request = $.getJSON('/movie/' + id + '/100', function(data) {
		var html = '';
		html += '';
		html += '
';
		html += '
' + data.title + '
';
		html += '
Genres: ';
		$.each(data.genres, function(index, value) {
			html += value;
			if (data.genres[index + 1]) {
				html += ' / ';
			}
		});
		html += '
';
		html += '
Rating: ' + data.vote_average + ' (' + data.vote_count + ' votes)
';
		html += '
Release date: ' + data.release_date + '
';
		html += '
' + data.overview + '
';
		html += '
';
		movie.html(html);
	});

	request.fail(function(jqXHR, textStatus) {
		movie.html('
Bad request!
');
	});
});

Tweak poster size a bit.


var util = require("util");
var MemoryCookieStore = require('tough-cookie/lib/memstore.js').MemoryCookieStore;
var CookieStorage = require('./cookie-storage');

function CookieMemoryStorage() {
    CookieStorage.call(this, new MemoryCookieStore())
}

util.inherits(CookieMemoryStorage, CookieStorage);
module.exports = CookieMemoryStorage;

CookieMemoryStorage.prototype.destroy = function(){

}

Implement .destroy method in MemoryCookieStorage.


'use strict'

let pokemons = ['Pikachu','Magikarp','Balbasaur'];

// ES 5
// pokemons.forEach(function(mon){
//     console.log(mon);
// });

// ES 6

'use strict'

let monsters = ['Slime','Orc','Skeleton Warrior'];

// ES 5
// monsters.forEach(function(mon){
//     console.log(mon);
// });

// ES 6

Update to avoid copyright problem


'use strict';

module.exports = function (grunt) {
  // Show elapsed time at the end
  require('time-grunt')(grunt);
  // Load all grunt tasks
  require('load-grunt-tasks')(grunt);

  // Project configuration.
  grunt.initConfig({
    nodeunit: {
      files: ['test/**/*_test.js']
    },
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      gruntfile: {
        src: 'Gruntfile.js'
      },
      lib: {
        src: ['lib/**/*.js']
      },
      test: {
        src: ['test/**/*.js']
      }
    },
    watch: {
      gruntfile: {
        files: '<%= jshint.gruntfile.src %>',
        tasks: ['jshint:gruntfile']
      },
      lib: {
        files: '<%= jshint.lib.src %>',
        tasks: ['jshint:lib', 'nodeunit']
      },
      test: {
        files: '<%= jshint.test.src %>',
        tasks: ['jshint:test', 'nodeunit']
      }
    }
  });

  // Default task.
  grunt.registerTask('default', ['jshint', 'nodeunit']);

};

'use strict';

module.exports = function (grunt) {
  // Show elapsed time at the end
  require('time-grunt')(grunt);
  // Load all grunt tasks
  require('load-grunt-tasks')(grunt);

  // Project configuration.
  grunt.initConfig({
    nodeunit: {
      files: ['test/**/*_test.js']
    },
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      gruntfile: {
        src: 'Gruntfile.js'
      },
      lib: {
        src: ['lib/**/*.js']
      },
      test: {
        src: ['test/**/*.js']
      }
    },
    watch: {
      gruntfile: {
        files: '<%= jshint.gruntfile.src %>',
        tasks: ['jshint:gruntfile']
      },
      lib: {
        files: '<%= jshint.lib.src %>',
        tasks: ['jshint:lib', 'nodeunit']
      },
      test: {
        files: '<%= jshint.test.src %>',
        tasks: ['jshint:test', 'nodeunit']
      }
    }
  });

  // Default task.
  grunt.registerTask('default', ['jshint', 'nodeunit']);

  // Nodeunit Test task.
  grunt.registerTask('test', ['nodeunit']);

};

Add a task to the Grunt file for just running the tests.


'use strict';

/* istanbul ignore next */
module.exports = function( grunt ) {
	require( './' )( __dirname, grunt )
		.addTodo( [ 'grunt/config/aliases.yaml' ] );
};

'use strict';

/* istanbul ignore next */
module.exports = function( grunt ) {
	require( './' )( __dirname, grunt )
		.addTodo( [ 'grunt/config/aliases.yaml' ] )
		.enableNpmPublish();
};

Revert "enablenpmpublish off for common branch"


/*
 * grunt-restful
 * https://github.com/DaneStuckel/grunt-restful
 *
 * Copyright (c) 2013 Dane Stuckel
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    // Configuration to be run (and then tested).
    'restful': {
        options: {
            uri: 'https://mingle.eng.shaw.ca/api/v2/projects/project_1_4_guide_program/cards/8756.xml',
            secure: false
        }
    }
  });

  // Actually load this plugin's task(s).
  grunt.loadTasks('tasks');

  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-clean');

  // Whenever the "test" task is run, first clean the "tmp" dir, then run this
  // plugin's task(s), then test the result.
  grunt.registerTask('test', ['restful']);

  // By default, lint and run all tests.
  grunt.registerTask('default', ['test']);

};

/*
 * grunt-restful
 * https://github.com/DaneStuckel/grunt-restful
 *
 * Copyright (c) 2013 Dane Stuckel
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    // Configuration to be run (and then tested).
    'restful': {
        options: {
        }
    }
  });

  // Actually load this plugin's task(s).
  grunt.loadTasks('tasks');

  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-clean');

  // Whenever the "test" task is run, first clean the "tmp" dir, then run this
  // plugin's task(s), then test the result.
  grunt.registerTask('test', ['restful']);

  // By default, lint and run all tests.
  grunt.registerTask('default', ['test']);

};

Remove local information from test settings


const EventEmitter = require("events");
class DemoClient extends EventEmitter {}

const Client = new DemoClient();
exports.client = Client;

Client.connect = () => {
  Client.emit("ready");
};

Client.on("ready", () => {
  console.log("Demo client ready");
});
Client.emit("ready");

Client.exec = (command, callback) => {
  var response = "";
  if(command === "gettime") {
    response = "Day 1, 07:00\n6 days to next horde.";
  }
  else if(command === "version") {
    response = "Game version: Alpha 17 (b240) Compatibility Version: Alpha 17 (Simulated)";
  }
  else if(command === "lp") {
    response = "Total of 0 in the game";
  }

  callback(void 0, response);
};

const EventEmitter = require("events");
class DemoClient extends EventEmitter {}

const Client = new DemoClient();
exports.client = Client;

Client.connect = () => {
  Client.emit("ready");
};

Client.on("ready", () => {
  console.log("Demo client ready");
});
Client.emit("ready");

Client.exec = (command, callback) => {
  var response = "";
  if(command === "gettime") {
    response = "Day 1, 07:00\n6 days to next horde.";
  }
  else if(command === "version") {
    response = "Game version: Alpha 18.1 (b8) Compatibility Version: Alpha 18.1";
  }
  else if(command === "lp") {
    response = "Total of 0 in the game";
  }

  callback(void 0, response);
};

Update demo server version response


'use strict';

var authHeader = require('auth-header');
var tokenUtils = require('./token');

module.exports = function (options) {
  return function (req, res, next) {
    var auth = authHeader.parse(req.get('authorization'));

    if (auth && auth.values && auth.values.length) {
      auth = auth.values[0];

      if (auth.scheme !== 'Bearer') {
        return res.status(400).json('Invalid authorization scheme, expected \'Bearer\'');
      }

      tokenUtils.decode(auth.token, options, function (err, decoded) {
        if (err) {
          console.error(err);
          req.authenticated = false;
        }

        req.authenticated = true;
        next();
      });
    }
    else {
      return res.status(400).json('Authorization token is missing');
    }
  };
};

'use strict';

var authHeader = require('auth-header');
var tokenUtils = require('./token');

module.exports = function (options) {
  return function (req, res, next) {
    var authorization = req.get('authorization');

    req.challenge = authorization;

    var auth = authHeader.parse(authorization);

    if (auth && auth.values && auth.values.length) {
      auth = auth.values[0];

      if (auth.scheme !== 'Bearer') {
        return res.status(400).json('Invalid authorization scheme, expected \'Bearer\'');
      }

      tokenUtils.decode(auth.token, options, function (err, decoded) {
        if (err) {
          console.error(err);
          req.authenticated = false;
          req.authentication = err;
        }
        else {
          req.authenticated = true;
          req.authentication = decoded;
        }

        next();
      });
    }
    else {
      return res.status(400).json('Authorization token is missing');
    }
  };
};

Send middlware output, challenge setup


import { DateUtils } from 'react-day-picker/lib/src/index';

export const convertModifiersToClassnames = (modifiers, theme) => {
  if (!modifiers) {
    return {};
  }

  return Object.keys(modifiers).reduce((convertedModifiers, key) => {
    return {
      ...convertedModifiers,
      [theme[key]]: modifiers[key],
    };
  }, {});
};

export const hasRange = selectedDays => {
  let containsFrom = false;
  let containsTo = false;

  if (!selectedDays || typeof selectedDays !== 'object') {
    return false;
  }

  Object.keys(selectedDays).map(key => {
    if (!selectedDays[key] || typeof selectedDays[key] !== 'object') {
      return;
    }

    Object.keys(selectedDays[key]).map(key => {
      if (key === 'from') {
        containsFrom = true;
      }
      if (key === 'to') {
        containsTo = true;
      }
    });
  });

  return containsFrom && containsTo;
};

export const isSelectingFirstDay = (from, to, day) => {
  const isBeforeFirstDay = from && DateUtils.isDayBefore(day, from);
  const isRangeSelected = from && to;
  return !from || isBeforeFirstDay || isRangeSelected;
};

import { DateUtils } from 'react-day-picker/lib/src/index';

export const convertModifiersToClassnames = (modifiers, theme) => {
  if (!modifiers) {
    return {};
  }

  return Object.keys(modifiers).reduce((convertedModifiers, key) => {
    return {
      ...convertedModifiers,
      [theme[key]]: modifiers[key],
    };
  }, {});
};

export const isSelectingFirstDay = (from, to, day) => {
  const isBeforeFirstDay = from && DateUtils.isDayBefore(day, from);
  const isRangeSelected = from && to;
  return !from || isBeforeFirstDay || isRangeSelected;
};

Delete this util function since we're not using it anymore


import { API } from 'api';
import Scenarios from 'api/scenarios';

describe('scenrios', () => {

  beforeEach(() => {
    API.clearStorage();
  });

  it('should get 0 scenarios', () => {
    expect(Scenarios.scenarios.length).toBe(0);
  });

  it('should add a scenario', () => {
    expect(Scenarios.scenarios.length).toBe(0);

    Scenarios.addScenario('test scenario');

    expect(Scenarios.scenarios.length).toBe(1);
    expect(Scenarios.scenarios[0].name).toBe('test scenario');
  });

});

import { API } from 'api';
import Scenarios from 'api/scenarios';

describe('scenarios', () => {

  beforeEach(() => {
    API.clearStorage();
  });

  it('should get 0 scenarios', () => {
    expect(Scenarios.scenarios.length).toBe(0);
  });

  it('should add a scenario', () => {
    expect(Scenarios.scenarios.length).toBe(0);

    Scenarios.addScenario('test scenario');

    expect(Scenarios.scenarios.length).toBe(1);
    expect(Scenarios.scenarios[0].name).toBe('test scenario');
  });

});

Fix typo in scenarios test


/*

Current state of affairs:

  - Gadget must be exported to window object, modules are not supported
    Proper support for modules will come later.
    Currently player is built with r.js and that breaks modules.

  - The name of the export on window object must equal to the "name" field in versal.json
    Player will look up this name on window object, after script is downloaded and evaluated

*/

window.SampleGadget = React.createClass({

  propTypes: {
    // Player API:
    //Indicates, whether the gadget is editable or not (author/learner modes)
    isEditable: React.PropTypes.bool.isRequired,
    // Use this function to persist attributes.
    // Attributes will be immediately sent back in props.
    // Won't do anything, if gadget is not editable.
    patchAttributes: React.PropTypes.func.isRequired,
    // Environment. Contains `sessionId` of current user and `apiUrl`
    environment: React.PropTypes.object,

    // Gadget-specific properties
    counter: React.PropTypes.number
  },

  getDefaultProps: function(){
    return {
      counter: 0
    }
  },

  render: function(){
    if(this.props.isEditable) {
      return React.DOM.button({ onClick: this.increaseCounter }, this.props.counter)
    } else {
      return React.DOM.div(null, 'Clicks so far:' + this.props.counter)
    }
  },

  increaseCounter: function(){
    this.props.patchAttributes({ counter: this.props.counter + 1 })
  }
})

/*

Current state of affairs:

  - Gadget must be exported to window object, modules are not supported
    Proper support for modules will come later.
    Currently player is built with r.js and that breaks modules.

  - The name of the export on window object must equal to the "name" field in versal.json
    Player will look up this name on window object, after script is downloaded and evaluated

*/

window.SampleGadget = React.createClass({

  propTypes: {
    gadget: React.PropTypes.shape({
      // Gadget API:

      //Indicates, whether the gadget is editable or not (author/learner modes)
      isEditable: React.PropTypes.bool.isRequired,
      // It won't do anything, if gadget is not editable.
      patchProps: React.PropTypes.func.isRequired,
      // Environment. Contains `sessionId` of current user and `apiUrl`
      environment: React.PropTypes.object
    }).isRequired,

    // Gadget-specific properties
    counter: React.PropTypes.number
  },

  getDefaultProps: function(){
    return {
      counter: 0
    }
  },

  render: function(){
    if(this.props.gadget.isEditable) {
      return React.DOM.button({ onClick: this.increaseCounter }, this.props.counter)
    } else {
      return React.DOM.div(null, 'Clicks so far:' + this.props.counter)
    }
  },

  increaseCounter: function(){
    this.props.gadget.patchProps({ counter: this.props.counter + 1 })
  }
})

Update react template to reflect latest player changes


'use strict';

var fs = require('fs');
var expect = require('chai').expect;
var bitmapReader = require('../lib/bitmapReader');

describe('bitmapReader', function() {
  before(function() {
    // remove ./img/newbmp.bmp if it exists.
    fs.unlinkSync('./img/newbmp.bmp');
  });

  it('should', function() {

  });
});

'use strict';

var fs = require('fs');
var expect = require('chai').expect;
var bitmapReader = require('../lib/bitmapReader');

describe('bitmapReader', function() {
  before(function() {
    // remove ./img/newbmp.bmp if it exists.
    try{
      fs.unlinkSync('./img/newbmp.bmp');
    } catch(err) {}
  });

  it('should', function() {

  });
});

Fix error in testing template


var mongoose = require('mongoose');
var test = require('nodeunit');
var Media = require('../../app/models/media');

var db;

db = mongoose.connect('mongodb://localhost/test');

module.exports = {

    "Should insert media with a title": function(test) {

		var media = new Media({ title: 'Lord of The Rings' });

		media.save(function(error, media) {
			if(error) console.log(error);

			test.equal(media.title, 'Lord of The Rings', '"Title" should be Lord of The Rings');
			test.done();
		});
	},

	"Should NOT insert media without a title": function(test) {

		var media = new Media();

		test.throws(media.save(function(error, media) {
			test.done();
		}));
	}
};

Media.remove({}, function(){});

var mongoose = require('mongoose');
var test = require('nodeunit');
var Media = require('../../app/models/media');

var db;

db = mongoose.connect('mongodb://localhost/test');

module.exports = {

    "Should insert media with a title": function(test) {

		var media = new Media({ title: 'Lord of The Rings' });

		media.save(function(error, media) {
			if(error) console.log(error);

			test.equal(media.title, 'Lord of The Rings', '"Title" should be Lord of The Rings');
			test.done();
		});
	},

	"Should NOT insert media without a title": function(test) {

		var media = new Media();

		test.throws(media.save(function(error, media) {
			test.done();
		}));
	},

	"Should insert media with one spoiler": function(test) {

		var media = new Media({
			title: 'Red Dragon'
		});

		media.spoilers.push("Graham kills Mr. D");

		media.save(function(error, media) {
			if(error) console.log(error);

			test.equal(media.spoilers[0], "Graham kills Mr. D");
			test.done()
		});
	}
};

Media.remove({}, function(){});

Test for spoiler list with single spoiler


import React from 'react';
import expandedViewPropTypes from './expandedViewPropTypes';
import {DIBELS_PSF} from '../reading/thresholds';
import {PHONOLOGICAL_AWARENESS} from './instructionalStrategies';
import GenericDibelsView from './GenericDibelsView';

export default function PhonemeSegmentationFluencyView(props) {
  return (
    <GenericDibelsView
      {...props}
      titleText="Phoneme segmentation fluency"
      benchmarkAssessmentKey={DIBELS_PSF}
      categoryKey={PHONOLOGICAL_AWARENESS}
      urls={MATERIAL_URLS}
    />
  );
}
PhonemeSegmentationFluencyView.propTypes = expandedViewPropTypes;

const MATERIAL_URLS = {
  'KF-winter': 'PhonemeSegmentationFluency-KF-winter',
  'KF-spring': 'PhonemeSegmentationFluency-KF-spring',
  '1-fall': 'PhonemeSegmentationFluency-1-fall'
};

import React from 'react';
import expandedViewPropTypes from './expandedViewPropTypes';
import {DIBELS_PSF} from '../reading/thresholds';
import {PHONOLOGICAL_AWARENESS} from './instructionalStrategies';
import GenericDibelsView from './GenericDibelsView';

export default function PhonemeSegmentationFluencyView(props) {
  return (
    <GenericDibelsView
      {...props}
      titleText="Phoneme segmentation fluency"
      benchmarkAssessmentKey={DIBELS_PSF}
      categoryKey={PHONOLOGICAL_AWARENESS}
      urls={MATERIAL_URLS}
    />
  );
}
PhonemeSegmentationFluencyView.propTypes = expandedViewPropTypes;

const MATERIAL_URLS = {
  'KF-winter': ['PhonemeSegmentationFluency-KF-winter'],
  'KF-spring': ['PhonemeSegmentationFluency-KF-spring'],
  '1-fall': ['PhonemeSegmentationFluency-1-fall']
};

Fix images of Phoneme Segmentation Fluency materials"


var test = require('tape');
var validate = require('../candidate');

var validCandidates = [
  { mid: 'data', candidate: 'a=candidate:2441410931 1 udp 2121670399 172.17.42.1 40992 typ host generation 0' }
];
var invalidCandidates = [
  { mid: 'data', candidate: 'a=candidate:2365396244 1 udp 1853300479 10.17.131.21 59229 typ  raddr 172.17.42.1 rport 59229 generation 0' }
];

test('valid candidates pass validation', function(t) {
  t.plan(validCandidates.length);

  validCandidates.forEach(function(candidate) {
    t.equal(validate(candidate).length, 0, 'validated ok');
  });
});

test('invalid candidates are detected', function(t) {
  t.plan(invalidCandidates.length);

  invalidCandidates.forEach(function(candidate) {
    var errors = validate(candidate);

    t.ok(errors.length > 0, 'validation error detected');
  });
});

var test = require('tape');
var validate = require('../candidate');

var validCandidates = [
  { mid: 'data', candidate: 'a=candidate:2441410931 1 udp 2121670399 172.17.42.1 40992 typ host generation 0' },
  'a=candidate:2441410931 1 udp 2121670399 172.17.42.1 40992 typ host generation 0'
];
var invalidCandidates = [
  { mid: 'data', candidate: 'a=candidate:2365396244 1 udp 1853300479 10.17.131.21 59229 typ  raddr 172.17.42.1 rport 59229 generation 0' },
  'a=candidate:2365396244 1 udp 1853300479 10.17.131.21 59229 typ  raddr 172.17.42.1 rport 59229 generation 0'
];

test('valid candidates pass validation', function(t) {
  t.plan(validCandidates.length);

  validCandidates.forEach(function(candidate) {
    t.equal(validate(candidate).length, 0, 'validated ok');
  });
});

test('invalid candidates are detected', function(t) {
  t.plan(invalidCandidates.length);

  invalidCandidates.forEach(function(candidate) {
    var errors = validate(candidate);

    t.ok(errors.length > 0, 'validation error detected');
  });
});

Add raw string tests to validation tests


/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes')
  , http = require('http')
  , mongoose = require('mongoose');

// TODO: Refactor db connection string
mongoose.connect('mongodb://localhost/nodewiki', function(err) {
    if (err) {
        console.log('Could not connect to database "' + 'mongodb://localhost/nodewiki' + '". Ensure that a mongo db instance is up and running.');
        process.exit(1);
    }
});

var app = express();
app.disable('x-powered-by');

app.configure(function(){
  app.set('port', process.env.PORT || 3000);
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.favicon());
  app.use(express.logger('dev'));
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

app.configure('development', function(){
  app.use(express.errorHandler());
});

app.all('*', routes.loadPage);
app.get('*', routes.showPage);
app.post('*', routes.savePage);

http.createServer(app).listen(app.get('port'), function(){
  console.log("Express server listening on port " + app.get('port'));
});

/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes')
  , http = require('http')
  , mongoose = require('mongoose');

// TODO: Refactor db connection string
mongoose.connect('mongodb://localhost/nodewiki', function(err) {
    if (err) {
        console.log('Could not connect to database "' + 'mongodb://localhost/nodewiki' + '". Ensure that a mongo db instance is up and running.');
        process.exit(1);
    }
});

var app = express();
app.disable('x-powered-by');

app.configure(function(){
  app.set('port', process.env.PORT || 3000);
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.favicon());
  app.use(express.logger('dev'));
  app.use(express.bodyParser());
  app.use(express.methodOverride());

  app.use(express.static(__dirname + '/public'));
  app.use(app.router);
});

app.configure('development', function(){
  app.use(express.errorHandler());
});

app.all('*', routes.loadPage);
app.get('*', routes.showPage);
app.post('*', routes.savePage);

http.createServer(app).listen(app.get('port'), function(){
  console.log("Express server listening on port " + app.get('port'));
});

Fix order of static and router to let static to handle static files first


'use strict';

var http = require('http');

var config = require('./lib/configLoader');
var urlUtils = require('./lib/urlUtils');
var api = require('./lib/api');

var logger = config.logger;

process.on('uncaughtException', function(err) {
  logger.error('Uncaught exception: %s', JSON.stringify(err, null, 4));
});

api.initChangesSync();

http.createServer(function(req, resp) {
  try {
    var isGet = req.method === 'GET';

    var module = urlUtils.module(req.url);
    var tarball = urlUtils.tarball(req.url);

    if (isGet && tarball) {
      api.manageAttachment(req, resp, tarball);
    } else if (isGet && module) {
      api.manageModule(req, resp, module);
    } else {
      api.proxyToCentralRepository(req, resp);
    }
  } catch (error) {
    logger.error(error);
  }
}).listen(config.port, function() {
  logger.info('Server running at http://localhost:%s with config:\n%s', config.port, JSON.stringify(config, null, 4));
});

'use strict';

var http = require('http');

var config = require('./lib/configLoader');
var urlUtils = require('./lib/urlUtils');
var api = require('./lib/api');

var logger = config.logger;

process.on('uncaughtException', function(err) {
  logger.error('Uncaught exception: %s', JSON.stringify(err, null, 4));
});

api.initChangesSync();

http.createServer(function(req, resp) {
  try {
    var startDate = Date.now();

    var isGet = req.method === 'GET';

    var module = urlUtils.module(req.url);
    var tarball = urlUtils.tarball(req.url);

    if (isGet && tarball) {
      api.manageAttachment(req, resp, tarball);
    } else if (isGet && module) {
      api.manageModule(req, resp, module);
    } else {
      api.proxyToCentralRepository(req, resp);
    }

    resp.on('finish', function() {
      logger.trace('%s: response in %sms', req.url, Date.now() - startDate);
    });
  } catch (error) {
    logger.error(error);
  }
}).listen(config.port, function() {
  logger.info('Server running at http://localhost:%s with config:\n%s', config.port, JSON.stringify(config, null, 4));
});

Add trace logs for response duration


(function(N) {
    N.Map = function(opts) {

        self = this;
        self.directionsService = new google.maps.DirectionsService();
        self.directionsDisplay = new google.maps.DirectionsRenderer();

        var mapOptions = {
             center: new google.maps.LatLng(39.875, -75.238),
             zoom: 13,
             mapTypeId: google.maps.MapTypeId.ROADMAP
        };

        var map = new google.maps.Map(document.getElementById("map"),mapOptions),
            traffic = new google.maps.TrafficLayer();

        traffic.setMap(map);
        self.directionsDisplay.setMap(map);

        // Update the travel times ever minute
        setInterval(self.calculateRoute, 60 * 1000);
    };

    N.Map.prototype.calculateRoute = function() {

        var self = this,
            start = $('#addr').val(),
            end = "Philadelphia International Airport",
            request = {
                origin: start,
                destination: end,
                travelMode: google.maps.TravelMode.DRIVING
            };

        self.directionsService.route(request, function(result, status) {
            if (status == google.maps.DirectionsStatus.OK) {
                self.directionsDisplay.setDirections(result);
                self.trigger('route-time-update', result.routes[0].legs[0]);
            }
        });
    };

    _.extend(N.Map.prototype, Backbone.Events);

}(this));

(function(N) {
    N.Map = function(opts) {

        self = this;
        self.directionsService = new google.maps.DirectionsService();
        self.directionsDisplay = new google.maps.DirectionsRenderer();

        var mapOptions = {
             center: new google.maps.LatLng(39.875, -75.238),
             zoom: 13,
             mapTypeId: google.maps.MapTypeId.ROADMAP
        };

        var map = new google.maps.Map(document.getElementById("map"),mapOptions),
            traffic = new google.maps.TrafficLayer();

        traffic.setMap(map);
        self.directionsDisplay.setMap(map);

        // Update the travel times ever minute
        setInterval($.proxy(self.calculateRoute,self), 60 * 1000);
    };

    N.Map.prototype.calculateRoute = function() {

        var self = this,
            start = $('#addr').val(),
            end = "Philadelphia International Airport",
            request = {
                origin: start,
                destination: end,
                travelMode: google.maps.TravelMode.DRIVING
            };

        self.directionsService.route(request, function(result, status) {
            if (status == google.maps.DirectionsStatus.OK) {
                self.directionsDisplay.setDirections(result);
                self.trigger('route-time-update', result.routes[0].legs[0]);
            }
        });
    };

    _.extend(N.Map.prototype, Backbone.Events);

}(this));

Update the driving time periodically


// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require twitter/bootstrap
//= require highstock
//= require highcharts-exporting
//= require leaflet
//= require json2
//= require underscore
//= require backbone
//
//= require workbench/workbench

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require twitter/bootstrap
//= require highstock
//= require highcharts-exporting
//= require leaflet
//= require json2
//= require underscore
//= require backbone
//= require geocens
//
//= require workbench/workbench

Add include for geocens-core JS


var SERVERDIR = __dirname + '/../server/src';

var express = require('express');
var config  = require(SERVERDIR + '/config');
var app = express();

app.use(express.static('../dashboard'))
app.use('/demo', express.static('./public'))

app.listen(config.app.demo_port, function () {
    console.log("Started dashboard on localhost:%s", config.app.demo_port);
    console.log("Started demo on localhost:%s/demo", config.app.demo_port);
});

require(SERVERDIR + '/app.js');

var SERVERDIR = __dirname + '/../server/src';

var path    = require('path');
var express = require('express');
var config  = require(SERVERDIR + '/config');
var app = express();

app.use('/demo', express.static('./public'));
app.use(express.static('../dashboard'), function (req, res) {
    if(/\/[^.]+$/.test(req.path))
        res.sendFile(path.resolve(__dirname + '/../dashboard/index.html'));
    else
        res.sendStatus(404);
});

app.listen(config.app.demo_port, function () {
    console.log("Started dashboard on localhost:%s", config.app.demo_port);
    console.log("Started demo on localhost:%s/demo", config.app.demo_port);
});

require(SERVERDIR + '/app.js');

Fix 404 issues on reload during demo


module.exports = {
    extends: ["eslint:recommended", "prettier"],
    root: true,
    env: {
        es6: "true",
        browser: true,
        node: true,
    },
    ignorePatterns: ["dist/", "docs/", "node_modules/"],
    rules: {
        "no-debugger": 1,
        "no-duplicate-imports": 1,
        // Do keep due avoid unintendet consequences.
        "no-alert": 0,
        "no-control-regex": 0,
        "no-self-assign": 0,
        "no-useless-escape": 0,
    },
    globals: {
        define: true,
        require: true,
        // tests
        it: true,
        describe: true,
        beforeEach: true,
        afterEach: true,
    },
};

module.exports = {
    extends: ["eslint:recommended", "prettier"],
    root: true,
    env: {
        es6: "true",
        browser: true,
        node: true,
        jest: true,
    },
    parser: "babel-eslint",
    ignorePatterns: ["dist/", "docs/", "node_modules/"],
    rules: {
        "no-debugger": 1,
        "no-duplicate-imports": 1,
        // Do keep due avoid unintendet consequences.
        "no-alert": 0,
        "no-control-regex": 0,
        "no-self-assign": 0,
        "no-useless-escape": 0,
    },
    globals: {},
};

Build infrastrucutre: eslint config adaption


module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
    mocha: true
  },
  extends: ['eslint:recommended', 'plugin:vue/essential', 'prettier'],
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module'
  },
  // required to lint *.vue files
  plugins: ['chai-expect', 'chai-friendly'],
  // add your custom rules here
  rules: {
    'no-console': [
      'error',
      {
        allow: ['warn', 'error']
      }
    ]
  },
  globals: {
    expect: true
  }
}

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
    mocha: true
  },
  extends: ['eslint:recommended', 'plugin:vue/recommended', 'prettier'],
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module'
  },
  // required to lint *.vue files
  plugins: ['chai-expect', 'chai-friendly'],
  // add your custom rules here
  rules: {
    'no-console': [
      'error',
      {
        allow: ['warn', 'error']
      }
    ],
    'no-trailing-spaces': 'error'
  },
  globals: {
    expect: true
  }
}

Use vue/recommended and add no-trailing-spaces


/*
 *  awa-storages -- Storages and folders
 *  Copyright (C) 2012 Stephane Carrez
 *  Written by Stephane Carrez (Stephane.Carrez@gmail.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
var AWA = {};

function init_folder_list(id, idCreate, idCurrent) {
    $(id).list({
        actionId: null,
        itemPrefix: 'folder-',
        editUrl: contextPath + '/storages/forms/create-folder-form.html',
        currentItem: '#folder-' + idCurrent,
        selectAction: function(element, item) {
            var id = element.getSelectedId(item);
            return ASF.Update(this, contextPath + '/storages/lists/documents.html?folderId=' + id, '#document-list-editor');
        }
    });
}

/*
 *  awa-storages -- Storages and folders
 *  Copyright (C) 2012, 2016 Stephane Carrez
 *  Written by Stephane Carrez (Stephane.Carrez@gmail.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
var AWA = {};

function init_folder_list(id, idCreate, idCurrent, page_url) {
    $(id).list({
        actionId: null,
        itemPrefix: 'folder-',
        editUrl: contextPath + '/storages/forms/create-folder-form.html',
        currentItem: '#folder-' + idCurrent,
        selectAction: function(element, item) {
            var id = element.getSelectedId(item);
            return ASF.Update(this, contextPath + page_url + '?folderId=' + id, '#document-list-editor');
        }
    });
}

Add page_url parameter to the init_folder_list function


import MainSidebar from './MainSidebar'
import UserPanel from './UserPanel'
import SearchForm from './SearchForm'
import Menu from './Menu'
import Item from './Item'

export default {
  MainSidebar,
  UserPanel,
  SearchForm,
  Menu,
  Item
}

import MainSidebar from './MainSidebar'
import UserPanel from './UserPanel'
import SearchForm from './SearchForm'
import Menu from './Menu'
import Item from './Item'

MainSidebar.UserPanel = UserPanel
MainSidebar.SearchForm = SearchForm
MainSidebar.Menu = Menu
MainSidebar.Item = Item

export default MainSidebar

Change the way of exports MainSidebar Component