<commit_before>var child_process = require('child_process');
var util = require('util');

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

  var bower = grunt.file.readJSON('bower.json');
  var components = [];
  for (var i in bower.concat.app) {
    components.push('components/' + bower.concat.app[i] + '/**/*.js');
  }

  var libtextsecurecomponents = [];
  for (i in bower.concat.libtextsecure) {
    libtextsecurecomponents.push('components/' + bower.concat.libtextsecure[i] + '/**/*.js');
  }

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    concat: {
      components: {
        src: components,
        dest: 'js/components.js',
      },
      libtextsecurecomponents: {
        src: libtextsecurecomponents,
        dest: 'libtextsecure/components.js',
      },
      test: {
        src: [
          'components/mocha/mocha.js',
          'components/chai/chai.js',
          'test/_test.js'
        ],
        dest: 'test/test.js',
      },
      //TODO: Move errors back down?
      libtextsecure: {
        options: {
          banner: ";(function() {\n",
          footer: "})();\n",
        },
        src: [
          'libtextsecure/errors.js',
          'libtextsecure/libaxolotl.js',
          'libtextsecure/axolotl_wrapper.js',

          'libtextsecure/crypto.js',
          'libtextsecure/storage.js',
          'libtextsecure/storage/user.js',
          'libtextsecure/storage/devices.js',
          'libtextsecure/storage/groups.js',
          'libtextsecure/protobufs.js',
          'libtextsecure/websocket.js',
          'libtextsecure/websocket-resources.js',
          'libtextsecure/helpers.js',
          'libtextsecure/stringview.js',
          'libtextsecure/api.js',
          'libtextsecure/account_manager.js',
          'libtextsecure/message_receiver.js',
          'libtextsecure/sendmessage.js',
          'libtextsecure/contacts_parser.js',
        ],
        dest: 'js/libtextsecure.js',
      },
      libtextsecuretest: {
        src: [
          'components/mock-socket/dist/mock-socket.js',
          'components/mocha/mocha.js',
          'components/chai/chai.js',
          'libtextsecure/test/_test.js'
        ],
        dest: 'libtextsecure/test/test.js',
      }
    },
    sass: {
        stylesheets: {
            files: {
                'stylesheets/manifest.css': 'stylesheets/manifest.scss',
                'stylesheets/options.css': 'stylesheets/options.scss'
            }
        }
    },
    jshint: {
      files: [
        'Gruntfile.js',
        'js/background.js',
        'js/chromium.js',
        'js/bimap.js',
        'js/conversation_panel.js',
        'js/database.js',
        'js/inbox_controller.js',
        'js/index.js',
        'js/libphonenumber-util.js',
        'js/options.js',
        'js/panel_controller.js',
        'js/models/*.js',
        'js/views/*.js',
      ],
      options: { jshintrc: '.jshintrc' },
    },
    dist: {
      src: [
        'manifest.json',
        'background.html',
        'index.html',
        'conversation.html',
        'options.html',
        'protos/*',
        'js/**',
        'stylesheets/*.css',
        '!js/register.js'
      ],
      res: [
        'images/**',
        'fonts/*',
      ]
    },
    copy: {
      res: {
        files: [{ expand: true, dest: 'dist/', src: ['<%= dist.res %>'] }],
      },
      src: {
        files: [{ expand: true, dest: 'dist/', src: ['<%= dist.src %>'] }],
        options: {
          process: function(content, srcpath) {
            if (srcpath.match('libtextsecure') || srcpath.match('background.js')) {
              return content.replace(
                /textsecure-service-staging.whispersystems.org/g,
                'textsecure-service-ca.whispersystems.org:4433').replace(
                /whispersystems-textsecure-attachments-staging.s3.amazonaws.com/g,
                'whispersystems-textsecure-attachments.s3.amazonaws.com');
            } else {
              return content;
            }
          }
        }
      }
    },
    jscs: {
      all: {
        src: ['js/**/*.js', '!js/libtextsecure.js', '!js/libaxolotl-worker.js', '!js/components.js', 'test/**/*.js']
      }
    },
    watch: {
      scripts: {
        files: ['<%= jshint.files %>', './js/**/*.js'],
        tasks: ['jshint']
      },
      sass: {
        files: ['./stylesheets/*.scss'],
        tasks: ['sass']
      },
      libtextsecure: {
        files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'],
        tasks: ['concat:libtextsecure']
      },
      dist: {
        files: ['<%= dist.src %>', '<%= dist.res %>'],
        tasks: ['copy']
      },
    },
    connect: {
      server: {
        options: {
          base: '.',
          port: 9999
        }
      }
    },
    'saucelabs-mocha': {
      all: {
        options: {
          urls: [
            'http://127.0.0.1:9999/test/index.html',
            'http://127.0.0.1:9999/libtextsecure/test/index.html',
          ],
          build: process.env.TRAVIS_JOB_ID,
          browsers: [
            { browserName: 'chrome', version: '40' },
            { platform: 'linux', browserName: 'firefox', version: '34' }
          ],
          testname: 'TextSecure-Browser Tests',
          'max-duration': 300,
          statusCheckAttempts: 200
        }
      }
    },
  });

  Object.keys(grunt.config.get('pkg').devDependencies).forEach(function(key) {
    if (/^grunt(?!(-cli)?$)/.test(key)) {  // ignore grunt and grunt-cli
      grunt.loadNpmTasks(key);
    }
  });

  grunt.registerTask('dev', ['connect', 'watch', 'sass']);
  grunt.registerTask('test', ['jshint', 'jscs', 'connect', 'saucelabs-mocha']);
  grunt.registerTask('default', ['preen', 'concat', 'sass', 'copy']);

};
<commit_msg>Tweak dev and default grunt tasks<commit_after>var child_process = require('child_process');
var util = require('util');

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

  var bower = grunt.file.readJSON('bower.json');
  var components = [];
  for (var i in bower.concat.app) {
    components.push('components/' + bower.concat.app[i] + '/**/*.js');
  }

  var libtextsecurecomponents = [];
  for (i in bower.concat.libtextsecure) {
    libtextsecurecomponents.push('components/' + bower.concat.libtextsecure[i] + '/**/*.js');
  }

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    concat: {
      components: {
        src: components,
        dest: 'js/components.js',
      },
      libtextsecurecomponents: {
        src: libtextsecurecomponents,
        dest: 'libtextsecure/components.js',
      },
      test: {
        src: [
          'components/mocha/mocha.js',
          'components/chai/chai.js',
          'test/_test.js'
        ],
        dest: 'test/test.js',
      },
      //TODO: Move errors back down?
      libtextsecure: {
        options: {
          banner: ";(function() {\n",
          footer: "})();\n",
        },
        src: [
          'libtextsecure/errors.js',
          'libtextsecure/libaxolotl.js',
          'libtextsecure/axolotl_wrapper.js',

          'libtextsecure/crypto.js',
          'libtextsecure/storage.js',
          'libtextsecure/storage/user.js',
          'libtextsecure/storage/devices.js',
          'libtextsecure/storage/groups.js',
          'libtextsecure/protobufs.js',
          'libtextsecure/websocket.js',
          'libtextsecure/websocket-resources.js',
          'libtextsecure/helpers.js',
          'libtextsecure/stringview.js',
          'libtextsecure/api.js',
          'libtextsecure/account_manager.js',
          'libtextsecure/message_receiver.js',
          'libtextsecure/sendmessage.js',
          'libtextsecure/contacts_parser.js',
        ],
        dest: 'js/libtextsecure.js',
      },
      libtextsecuretest: {
        src: [
          'components/mock-socket/dist/mock-socket.js',
          'components/mocha/mocha.js',
          'components/chai/chai.js',
          'libtextsecure/test/_test.js'
        ],
        dest: 'libtextsecure/test/test.js',
      }
    },
    sass: {
        stylesheets: {
            files: {
                'stylesheets/manifest.css': 'stylesheets/manifest.scss',
                'stylesheets/options.css': 'stylesheets/options.scss'
            }
        }
    },
    jshint: {
      files: [
        'Gruntfile.js',
        'js/background.js',
        'js/chromium.js',
        'js/bimap.js',
        'js/conversation_panel.js',
        'js/database.js',
        'js/inbox_controller.js',
        'js/index.js',
        'js/libphonenumber-util.js',
        'js/options.js',
        'js/panel_controller.js',
        'js/models/*.js',
        'js/views/*.js',
      ],
      options: { jshintrc: '.jshintrc' },
    },
    dist: {
      src: [
        'manifest.json',
        'background.html',
        'index.html',
        'conversation.html',
        'options.html',
        'protos/*',
        'js/**',
        'stylesheets/*.css',
        '!js/register.js'
      ],
      res: [
        'images/**',
        'fonts/*',
      ]
    },
    copy: {
      res: {
        files: [{ expand: true, dest: 'dist/', src: ['<%= dist.res %>'] }],
      },
      src: {
        files: [{ expand: true, dest: 'dist/', src: ['<%= dist.src %>'] }],
        options: {
          process: function(content, srcpath) {
            if (srcpath.match('libtextsecure') || srcpath.match('background.js')) {
              return content.replace(
                /textsecure-service-staging.whispersystems.org/g,
                'textsecure-service-ca.whispersystems.org:4433').replace(
                /whispersystems-textsecure-attachments-staging.s3.amazonaws.com/g,
                'whispersystems-textsecure-attachments.s3.amazonaws.com');
            } else {
              return content;
            }
          }
        }
      }
    },
    jscs: {
      all: {
        src: ['js/**/*.js', '!js/libtextsecure.js', '!js/libaxolotl-worker.js', '!js/components.js', 'test/**/*.js']
      }
    },
    watch: {
      scripts: {
        files: ['<%= jshint.files %>', './js/**/*.js'],
        tasks: ['jshint']
      },
      sass: {
        files: ['./stylesheets/*.scss'],
        tasks: ['sass']
      },
      libtextsecure: {
        files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'],
        tasks: ['concat:libtextsecure']
      },
      dist: {
        files: ['<%= dist.src %>', '<%= dist.res %>'],
        tasks: ['copy']
      },
    },
    connect: {
      server: {
        options: {
          base: '.',
          port: 9999
        }
      }
    },
    'saucelabs-mocha': {
      all: {
        options: {
          urls: [
            'http://127.0.0.1:9999/test/index.html',
            'http://127.0.0.1:9999/libtextsecure/test/index.html',
          ],
          build: process.env.TRAVIS_JOB_ID,
          browsers: [
            { browserName: 'chrome', version: '40' },
            { platform: 'linux', browserName: 'firefox', version: '34' }
          ],
          testname: 'TextSecure-Browser Tests',
          'max-duration': 300,
          statusCheckAttempts: 200
        }
      }
    },
  });

  Object.keys(grunt.config.get('pkg').devDependencies).forEach(function(key) {
    if (/^grunt(?!(-cli)?$)/.test(key)) {  // ignore grunt and grunt-cli
      grunt.loadNpmTasks(key);
    }
  });

  grunt.registerTask('dev', ['default', 'connect', 'watch']);
  grunt.registerTask('test', ['jshint', 'jscs', 'connect', 'saucelabs-mocha']);
  grunt.registerTask('default', ['concat', 'sass', 'copy']);

};
<|endoftext|>


<commit_before>(function( window ) {
	'use strict';

	var history = window.history;
	var exitElement = document.getElementById( 'quick-exit' );
	var accessKeyCode;
	var eventFunction;

	function quickExit( event ) {
		window.document.body.style.opacity = 0;
		window.document.title = 'New Tab';
		// clears current frame only
		document.write( '' ); // eslint-disable-line no-implied-eval

		if ( history && history.replaceState ) {
			history.replaceState( null, 'Home', '/' );
		}

		window.location = this.href;

		event.preventDefault();
		return false;
	}

	function quickExitKeyboard( event ) {
		if ( event.keyCode === accessKeyCode ) {
			if ( /^(INPUT|TEXTAREA|SELECT)$/i.test( event.target.tagName )) {
				return;
			}
			quickExit.call( exitElement, event );
		}
	}

	if ( ! exitElement ) {
		return;
	}

	accessKeyCode = exitElement.getAttribute( 'accesskey' ).toUpperCase().charCodeAt( 0 );

	if ( document.addEventListener ) {
		eventFunction = 'addEventListener';
	} else if ( document.attachEvent ) {
		eventFunction = 'attachEvent';
	}
	if ( eventFunction ) {
		exitElement[ eventFunction ]( 'click', quickExit, true );
		document[ eventFunction ]( 'keydown', quickExitKeyboard, true );
	}

}( window.top ));
<commit_msg>jshint config for source js<commit_after>(function( window ) {
	'use strict';

	var history = window.history;
	var exitElement = document.getElementById( 'quick-exit' );
	var accessKeyCode;
	var eventFunction;

	function quickExit( event ) {
		window.document.body.style.opacity = 0;
		window.document.title = 'New Tab';
		// clears current frame only
		/* jshint -W060 */
		document.write( '' ); // eslint-disable-line no-implied-eval
		/* jshint +W060 */

		if ( history && history.replaceState ) {
			history.replaceState( null, 'Home', '/' );
		}

		/* jshint -W040 */
		window.location = this.href;
		/* jshint +W040 */

		event.preventDefault();
		return false;
	}

	function quickExitKeyboard( event ) {
		if ( event.keyCode === accessKeyCode ) {
			if ( /^(INPUT|TEXTAREA|SELECT)$/i.test( event.target.tagName )) {
				return;
			}
			quickExit.call( exitElement, event );
		}
	}

	if ( ! exitElement ) {
		return;
	}

	accessKeyCode = exitElement.getAttribute( 'accesskey' ).toUpperCase().charCodeAt( 0 );

	if ( document.addEventListener ) {
		eventFunction = 'addEventListener';
	} else if ( document.attachEvent ) {
		eventFunction = 'attachEvent';
	}
	if ( eventFunction ) {
		exitElement[ eventFunction ]( 'click', quickExit, true );
		document[ eventFunction ]( 'keydown', quickExitKeyboard, true );
	}

}( window.top ));
<|endoftext|>


<commit_before>/**
 * Create a hierarchical menu using multiple attributes
 * @function hierarchicalMenu
 * @param  {string|DOMElement} options.container CSS Selector or DOMElement to insert the widget
 * @param  {string[]} options.attributes Array of attributes to use to generate the hierarchy of the menu.
 * See the example for the convention to follow.
 * @param  {number} [options.limit=10] How much facet values to get [*]
 * @param  {string} [options.separator=">"] Separator used in the attributes to separate level values. [*]
 * @param  {string} [options.rootPath] Prefix path to use if the first level is not the root level.
 * @param  {string} [options.showParentLevel=false] Show the parent level of the current refined value
 * @param  {string[]|Function} [options.sortBy=['name:asc']] How to sort refinements. Possible values: `count|isRefined|name:asc|name:desc`.
 *   You can also use a sort function that behaves like the standard Javascript [compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Syntax).
 * @param  {Object} [options.templates] Templates to use for the widget
 * @param  {string|Function} [options.templates.header=''] Header template (root level only)
 * @param  {string|Function} [options.templates.item] Item template, provided with `name`, `count`, `isRefined`, `url` data properties
 * @param  {string|Function} [options.templates.footer=''] Footer template (root level only)
 * @param  {Function} [options.transformData.item] Method to change the object passed to the `item` template
 * @param  {boolean} [options.autoHideContainer=true] Hide the container when there are no items in the menu
 * @param  {Object} [options.cssClasses] CSS classes to add to the wrapping elements
 * @param  {string|string[]} [options.cssClasses.root] CSS class to add to the root element
 * @param  {string|string[]} [options.cssClasses.header] CSS class to add to the header element
 * @param  {string|string[]} [options.cssClasses.body] CSS class to add to the body element
 * @param  {string|string[]} [options.cssClasses.footer] CSS class to add to the footer element
 * @param  {string|string[]} [options.cssClasses.list] CSS class to add to the list element
 * @param  {string|string[]} [options.cssClasses.item] CSS class to add to each item element
 * @param  {string|string[]} [options.cssClasses.depth] CSS class to add to each item element to denote its depth. The actual level will be appended to the given class name (ie. if `depth` is given, the widget will add `depth0`, `depth1`, ... according to the level of each item).
 * @param  {string|string[]} [options.cssClasses.active] CSS class to add to each active element
 * @param  {string|string[]} [options.cssClasses.link] CSS class to add to each link (when using the default template)
 * @param  {string|string[]} [options.cssClasses.count] CSS class to add to each count element (when using the default template)
 * @param  {object|boolean} [options.collapsible=false] Hide the widget body and footer when clicking on header
 * @param  {boolean} [options.collapsible.collapsed] Initial collapsed state of a collapsible widget
 * @return {Object}
 */
const usage = `Usage:
connectHierarchicalMenu({
  attributes,
  [ separator=' > ' ],
  [ rootPath ],
  [ showParentLevel=true ],
  [ limit=10 ],
  [ sortBy=['name:asc'] ],
})`;
const connectHierarchicalMenu = renderHierarchicalMenu => ({
    attributes,
    separator = ' > ',
    rootPath = null,
    showParentLevel = true,
    limit = 10,
    sortBy = ['name:asc'],
  } = {}) => {
  if (!attributes || !attributes.length) {
    throw new Error(usage);
  }

  // we need to provide a hierarchicalFacet name for the search state
  // so that we can always map $hierarchicalFacetName => real attributes
  // we use the first attribute name
  const hierarchicalFacetName = attributes[0];

  return {
    getConfiguration: currentConfiguration => ({
      hierarchicalFacets: [{
        name: hierarchicalFacetName,
        attributes,
        separator,
        rootPath,
        showParentLevel,
      }],
      maxValuesPerFacet: currentConfiguration.maxValuesPerFacet !== undefined ?
        Math.max(currentConfiguration.maxValuesPerFacet, limit) :
        limit,
    }),
    init({helper, createURL, instantSearchInstance}) {
      this._refine = facetValue => helper
        .toggleRefinement(hierarchicalFacetName, facetValue)
        .search();

      // Bind createURL to this specific attribute
      function _createURL(facetValue) {
        return createURL(helper.state.toggleRefinement(hierarchicalFacetName, facetValue));
      }

      renderHierarchicalMenu({
        attributeNameKey: 'path',
        createURL: _createURL,
        items: [],
        templateProps: this._templateProps,
        refine: this._refine,
        instantSearchInstance,
      }, true);
    },
    _prepareFacetValues(facetValues, state) {
      return facetValues
        .slice(0, limit)
        .map(subValue => {
          if (Array.isArray(subValue.data)) {
            subValue.data = this._prepareFacetValues(subValue.data, state);
          }

          return subValue;
        });
    },
    render({results, state, createURL, instantSearchInstance}) {
      const items = this._prepareFacetValues(
        results.getFacetValues(hierarchicalFacetName, {sortBy}).data || [],
        state
      );

      // Bind createURL to this specific attribute
      function _createURL(facetValue) {
        return createURL(state.toggleRefinement(hierarchicalFacetName, facetValue));
      }

      renderHierarchicalMenu({
        attributeNameKey: 'path',
        createURL: _createURL,
        items,
        refine: this._refine,
        instantSearchInstance,
      }, false);
    },
  };
};

export default connectHierarchicalMenu;
<commit_msg>doc(connectors): connectHierarchicalMenu jsdoc<commit_after>import {checkRendering} from '../../lib/utils.js';

const usage = `Usage:
var customToggle = connectHierarchicalMenu(function render(params, isFirstRendering) {
  // params = {
  //   attributeNameKey,
  //   createURL,
  //   items,
  //   refine,
  //   instantSearchInstance,
  // }
});
search.addWidget(
  customToggle({
    attributes,
    [ separator = ' > ' ],
    [ rootPath = null ],
    [ showParentLevel = true ],
    [ limit = 10 ],
    [ sortBy = ['name:asc'] ],
  });
);
Full documentation available at https://community.algolia.com/instantsearch.js/connectors/connectHierarchicalMenu.html
`;

/**
 * @typedef {Object} CustomHierarchicalMenuWidgetOptions
 * @param {string[]} attributes Array of attributes to use to generate the hierarchy of the menu.
 * @param  {string} [separator='>'] Separator used in the attributes to separate level values. [*]
 * @param  {string} [rootPath] Prefix path to use if the first level is not the root level.
 * @param  {string} [showParentLevel=false] Show the parent level of the current refined value
 * @param  {number} [limit=10] How much facet values to get [*]
 * @param  {string[]|Function} [sortBy=['name:asc']] How to sort refinements. Possible values: `count|isRefined|name:asc|name:desc`.
 *   You can also use a sort function that behaves like the standard Javascript [compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Syntax).
 */

/**
 * @typedef {Object} HierarchicalMenuRenderingOptions
 * @property {string} attributeNameKey
 * @property {function} createURL
 * @property {Object[]} items
 * @property {function} refine
 * @property {InstantSearch} instantSearchInstance
 */

 /**
  * Connects a rendering function with the toggle business logic.
  * @param {function(HierarchicalMenuRenderingOptions)} renderFn function that renders the toggle widget
  * @return {function(CustomHierarchicalMenuWidgetOptions)} a widget factory for toggle widget
  */
export default function connectHierarchicalMenu(renderFn) {
  checkRendering(renderFn, usage);

  return ({
    attributes,
    separator = ' > ',
    rootPath = null,
    showParentLevel = true,
    limit = 10,
    sortBy = ['name:asc'],
  } = {}) => {
    if (!attributes || !attributes.length) {
      throw new Error(usage);
    }

    // we need to provide a hierarchicalFacet name for the search state
    // so that we can always map $hierarchicalFacetName => real attributes
    // we use the first attribute name
    const [hierarchicalFacetName] = attributes;

    return {
      getConfiguration: currentConfiguration => ({
        hierarchicalFacets: [{
          name: hierarchicalFacetName,
          attributes,
          separator,
          rootPath,
          showParentLevel,
        }],
        maxValuesPerFacet: currentConfiguration.maxValuesPerFacet !== undefined ?
          Math.max(currentConfiguration.maxValuesPerFacet, limit) :
          limit,
      }),

      init({helper, createURL, instantSearchInstance}) {
        this._refine = facetValue => helper
          .toggleRefinement(hierarchicalFacetName, facetValue)
          .search();

        // Bind createURL to this specific attribute
        function _createURL(facetValue) {
          return createURL(helper.state.toggleRefinement(hierarchicalFacetName, facetValue));
        }

        renderFn({
          attributeNameKey: 'path',
          createURL: _createURL,
          items: [],
          refine: this._refine,
          instantSearchInstance,
        }, true);
      },

      _prepareFacetValues(facetValues, state) {
        return facetValues
          .slice(0, limit)
          .map(subValue => {
            if (Array.isArray(subValue.data)) {
              subValue.data = this._prepareFacetValues(subValue.data, state);
            }

            return subValue;
          });
      },

      render({results, state, createURL, instantSearchInstance}) {
        const items = this._prepareFacetValues(
          results.getFacetValues(hierarchicalFacetName, {sortBy}).data || [],
          state
        );

        // Bind createURL to this specific attribute
        function _createURL(facetValue) {
          return createURL(state.toggleRefinement(hierarchicalFacetName, facetValue));
        }

        renderFn({
          attributeNameKey: 'path',
          createURL: _createURL,
          items,
          refine: this._refine,
          instantSearchInstance,
        }, false);
      },
    };
  };
}
<|endoftext|>


<commit_before>var Future = Npm.require( 'fibers/future');
var DAY = 1000 * 60 * 60 * 24;
var daysStoreArticles = 3.0;
var updateInterval = 1000 * 60 * 15;
var intervalProcesses = {};
var articlePubLimit = 150;
var maxArticlesFromSource = 25;
var feedSubscriber;
var keepLimitDate = function(){
  return new Date( new Date().getTime() - ( DAY * daysStoreArticles ));
};

Articles._ensureIndex( { feed_id: 1, date: -1} );

Accounts.config({sendVerificationEmail: true});

Facts.setUserIdFilter(function ( userId ) {
  var user = Meteor.users.findOne(userId);
  return user && user.admin;
});

/************* null publication should be sent without fastRender.onAllRoutes()
FastRender.onAllRoutes( function( ) {
  var self = this,
      feed_ids,
      visibleFields = {_id: 1, title: 1, source: 1, date: 1, summary: 1, link: 1, clicks: 1, readCount: 1};

  if ( self.userId )
    feed_ids = Meteor.users.findOne( self.userId, {fields: {feedList: 1}} ).feedList;
  else
    feed_ids = Feeds.find( {subscribers: null}, {fields: {_id: 1}}).map ( function( feed ) { return feed._id;});

  //self.subscribe( 'feeds' );
  //self.subscribe( "articles", feed_ids, 20);
});
****************/
function stoppablePublisher( sub ){
  var self = this,
      handle,
      name;

  function subHasId( id ){
    return sub._documents && sub._documents[ name ] && sub._documents[ name ][ id ];
  };

  self.ids = () => {
    return _( sub._documents && sub._documents[ name ] || {} ).keys();
  }

  function observeAndPublish( cursor ){
    var oldIds = self.ids(),
        newIds;

    handle = cursor.observeChanges({
      added( id, doc ){
        if ( subHasId( id ) ){
          oldIds.splice( oldIds.indexOf( id ), 1);
        } else{
          sub.added( name, id, doc );
        }
      },
      removed ( id ){
        sub.removed( name, id );
      },
      changed ( id, doc ){
        sub.changed( name, id, doc );
      }
    });

    if ( sub._documents && oldIds.length)
      oldIds.forEach( ( id ) => sub.removed ( name, id) );
  }

  self.start = ( cursor ) =>{
    if ( handle ) handle.stop();
    if ( cursor._cursorDescription.collectionName !== name ){
      if ( ! name )
        name = cursor._cursorDescription.collectionName;
      else
        throw new Error( 'stoppablePublisher can not handle cursors from different collections. ',
         name, ' to ', cursor._cursorDescription.collectionName);
    }
    observeAndPublish( cursor );
  };

  self.stop = () => {
    handle && handle.stop();
  }
}

//prepare userdata and feedlist for all clients ASAP
Meteor.publish( null, function() {
  var self = this;
  var userId = self.userId || 'nullUser';
  var feedOptions = {fields: {_id: 1, title: 1, url: 1, last_date:1}};
  var articleFields = {_id: 1, title: 1, source: 1, date: 1, summary: 1, link: 1, feed_id: 1};
  var articleOptions = {fields: articleFields, limit: 100, sort: {date: -1, _id: 1}};
  var feedPublisher, articlePublisher, userObserver, nullUserObserver, nullUserCursor;

  feedPublisher = new stoppablePublisher( self );
  articlePublisher = new stoppablePublisher( self );

  function startFeedsAndArticles( id, doc){
    if ( ! doc || ! doc.feedList || doc.feedList.length === 0 )
      return;
    var feedCursor = Feeds.find( {_id:{ $in: doc.feedList}}, feedOptions);
    feedPublisher.start( feedCursor );
    var articleCursor = Articles.find( {feed_id: {$in: doc.feedList}, date: {$gt: keepLimitDate()}}, articleOptions);
    articlePublisher.start( articleCursor );
  }

  userObserver = Meteor.users.find( userId, {fields: {feedList: 1}} ).observeChanges({
    added: startFeedsAndArticles,
    changed: startFeedsAndArticles
  });

  self.onStop( () => {
    userObserver.stop();
    feedPublisher.stop();
    delete self.feedPublisher;
    articlePublisher.stop();
    delete self.articlePublisher;
  });

  return Meteor.users.find( userId, {fields: {admin: 1}});
});

Meteor.publish( 'articles', function( feed_ids, limit ){
  var self = this;
  check( feed_ids, [String] );
  check( limit, Number );
  var startDate = keepLimitDate();
  var visibleFields = {_id: 1, title: 1, source: 1, date: 1, summary: 1, link: 1, feed_id: 1};
  return Articles.find( {feed_id: {$in: feed_ids}, date: {$gt: startDate}}, {fields: visibleFields, limit: limit, sort: {date: -1, _id: 1}});
});

Meteor.startup( () => {

  Meteor.call('findArticles', {} );
  Meteor.call('removeOldArticles');

  intervalProcesses["removeOldArticles"] = Meteor.setInterval(
    () => Meteor.call('removeOldArticles'),
    DAY
  );

  intervalProcesses[ "findArticles"] = Meteor.setInterval(
    () => Meteor.call('findArticles', { hub: null} ),
    updateInterval
  );

  var options = {
    callbackUrl: Meteor.absoluteUrl("hubbub"),
    secret: Random.id()
  };

  feedSubscriber = new FeedSubscriber ( options );

  feedSubscriber.on( 'feedStream',
    Meteor.bindEnvironment( function( stream, topic ) {
      var feed = feedSubscriber.subscriptions[ topic ];
      FeedParser.readAndInsertArticles( stream, feed )
    }, ( error ) => console.log( error ) )
  );

  process.on('exit', Meteor.bindEnvironment ( function (){
    feedSubscriber.stopAllSubscriptions();
    console.log( "paused to allow subscriptions to end");

  }, function ( e ) { throw e; }));

  _.each(['SIGINT', 'SIGHUP', 'SIGTERM'], function (sig) {
    process.once(sig, Meteor.bindEnvironment (function () {
      console.log ( "process received : " + sig);
      feedSubscriber.stopAllSubscriptions();
      process.kill( process.pid, sig);
    }, function ( e ) { throw e; }));
  });

  var handle = Feeds.find({hub: {$ne: null}},{fields: {_id: 1, hub:1, url:1}}).observeChanges({

    added: function ( id, fields ){
      fields._id = id;
      feedSubscriber.subscriptions[ fields.url ] = fields;
      feedSubscriber.subscribe ( fields.url, fields.hub );
    },

    removed: function( id ){
      var fields = _( feedSubscriber.subscriptions ).findWhere( {_id: id});
      fields.unsub = new Future();
      feedSubscriber.unsubscribe( fields.url, fields.hub );
    },

    changed: function ( id, fields ){
      var oldSub = _( feedSubscriber.subscriptions ).findWhere({_id :id });
      fields = _( fields ).default( oldSub );
      oldSub.unsub = new Future();
	    feedSubscriber.unsubscribe( oldSub.url, oldSub.hub );
      oldSub.unsub.wait();
      feedSubscriber.subscriptions[ fields.url ] = fields;
	    feedSubscriber.subscribe ( fields.url, fields.hub );
    }
  });
});

Meteor.methods({

  findArticles: function( criteria ) {
    check ( criteria,  Object );
    console.time("findArticles");
    criteria = criteria || {};
    var article_count = 0;
    var feeds = Feeds.find( criteria );
    if ( feeds.count() < 1) return;
    var rssResults = FeedParser.syncFP( feeds.fetch() );

    rssResults.forEach( function( rssResult ){
      if ( rssResult.statusCode === 200 ) {
        Feeds.update(rssResult._id, {$set: _( rssResult ).pick( 'lastModified', 'etag', 'lastDate' ) } );
      }
      else if ( rssResult.error ) console.log (rssResult.url + " returned " + rssResult.error);
      else if ( typeof rssResult.statusCode === "number" && rssResult.statusCode !== 304 ){
        console.log( rssResult.url + " responded with " + rssResult.statusCode );
      }
    });

    console.timeEnd("findArticles");

  },

  stopAndRestartPubSub: function(){
    feedSubscriber.stopAllSubscriptions();

    var onErr = function(err, res){ if( err ) console.error( err );};
    var onFeed = function( feed ){
      feedSubscriber.subscriptions[ feed.url ] = feed;
      feedSubscriber.subscribe( feed.url, feed.hub , feed._id, onErr );
    };

    Feeds.find({hub: {$ne: null}},{fields: {_id: 1, hub:1, url:1}}).forEach( onFeed );
  },

  removeOldArticles: function(){
    console.log("removeOldArticles method called on server");
    var error = Articles.remove({date:  {$lt: keepLimitDate()}, clicks: 0 }, function(error){ return error;});
    return error || 'success';
  },

  addSubscriberToFeeds: function(){
    var self = this;
    Feeds.find({}).forEach( function ( feed ){
      console.log("adding subscriber " + self.userId);
      Feeds.update( feed._id,{ $addToSet: { subscribers: self.userId }});
    });
  },

  createNullSubscriber: function(){
    var feedList = Feeds.find( {subscribers: null}, {fields: {_id:1}}).map( feed => feed._id );
    Meteor.users.upsert( 'nullUser', { _id: 'nullUser', feedList }, error => {
      if ( error ) console.log( error );
    });
  },

  addFeed_idToArticles: function(){
    Articles.find({}).forEach( function (article){
      var feed_id = Feeds.findOne({title: article.source})._id;
      Articles.update(article._id,{$set: {feed_id: feed_id}});
    });

  },

  cleanUrls: function(){
    Feeds.find({}).forEach( function(feed){
      var result = FeedParser.syncFP( feed );
      if (result && result.url && feed.url !== result.url ){
	      console.log("changing url " + feed.url + " to " + result.url);
	      Feeds.update(feed._id, {$set: {url: result.url }});
      }
    });
  },

  markRead: function( link ){
    var self = this;
    check( link, String );
    var article = Articles.findOne({link: link});
    if ( article ){
      Articles.update( article._id,{$addToSet: {readBy: this.userId }, $inc: {clicks: 1, readCount: 1}}, function( error, result){
	//make update async since client might be waiting to navigate
      });
    }
    console.log( "marked as read: " + link);
  },

  XML2JSparse: function ( file ) {
    check( file, String);
    return XML2JS.parse( file );
  },

  createFeedListByUser: function(){
    Meteor.users.find( {}, {_id:1} ).forEach( function ( user ){
      var feedList = Feeds.find( {subscribers: user._id}, {_id: 1} ).map( function( feed ) { return feed._id });
      Meteor.users.update( user._id, {$set: {feedList: feedList}, $pull:{feedList: null}});
    //  Meteor.users.update( user._id, { $pull:{feedList: null}});
    });
  },

  checkAdmin: function(){
    var user = Meteor.users.findOne( {_id: this.userId} );
    return user && user.admin;
  }

});
<commit_msg>fix arguments in subscribe<commit_after>var Future = Npm.require( 'fibers/future');
var DAY = 1000 * 60 * 60 * 24;
var daysStoreArticles = 3.0;
var updateInterval = 1000 * 60 * 15;
var intervalProcesses = {};
var articlePubLimit = 150;
var maxArticlesFromSource = 25;
var feedSubscriber;
var keepLimitDate = function(){
  return new Date( new Date().getTime() - ( DAY * daysStoreArticles ));
};

Articles._ensureIndex( { feed_id: 1, date: -1} );

Accounts.config({sendVerificationEmail: true});

Facts.setUserIdFilter(function ( userId ) {
  var user = Meteor.users.findOne(userId);
  return user && user.admin;
});

/************* null publication should be sent without fastRender.onAllRoutes()
FastRender.onAllRoutes( function( ) {
  var self = this,
      feed_ids,
      visibleFields = {_id: 1, title: 1, source: 1, date: 1, summary: 1, link: 1, clicks: 1, readCount: 1};

  if ( self.userId )
    feed_ids = Meteor.users.findOne( self.userId, {fields: {feedList: 1}} ).feedList;
  else
    feed_ids = Feeds.find( {subscribers: null}, {fields: {_id: 1}}).map ( function( feed ) { return feed._id;});

  //self.subscribe( 'feeds' );
  //self.subscribe( "articles", feed_ids, 20);
});
****************/
function stoppablePublisher( sub ){
  var self = this,
      handle,
      name;

  function subHasId( id ){
    return sub._documents && sub._documents[ name ] && sub._documents[ name ][ id ];
  };

  self.ids = () => {
    return _( sub._documents && sub._documents[ name ] || {} ).keys();
  }

  function observeAndPublish( cursor ){
    var oldIds = self.ids(),
        newIds;

    handle = cursor.observeChanges({
      added( id, doc ){
        if ( subHasId( id ) ){
          oldIds.splice( oldIds.indexOf( id ), 1);
        } else{
          sub.added( name, id, doc );
        }
      },
      removed ( id ){
        sub.removed( name, id );
      },
      changed ( id, doc ){
        sub.changed( name, id, doc );
      }
    });

    if ( sub._documents && oldIds.length)
      oldIds.forEach( ( id ) => sub.removed ( name, id) );
  }

  self.start = ( cursor ) =>{
    if ( handle ) handle.stop();
    if ( cursor._cursorDescription.collectionName !== name ){
      if ( ! name )
        name = cursor._cursorDescription.collectionName;
      else
        throw new Error( 'stoppablePublisher can not handle cursors from different collections. ',
         name, ' to ', cursor._cursorDescription.collectionName);
    }
    observeAndPublish( cursor );
  };

  self.stop = () => {
    handle && handle.stop();
  }
}

//prepare userdata and feedlist for all clients ASAP
Meteor.publish( null, function() {
  var self = this;
  var userId = self.userId || 'nullUser';
  var feedOptions = {fields: {_id: 1, title: 1, url: 1, last_date:1}};
  var articleFields = {_id: 1, title: 1, source: 1, date: 1, summary: 1, link: 1, feed_id: 1};
  var articleOptions = {fields: articleFields, limit: 100, sort: {date: -1, _id: 1}};
  var feedPublisher, articlePublisher, userObserver, nullUserObserver, nullUserCursor;

  feedPublisher = new stoppablePublisher( self );
  articlePublisher = new stoppablePublisher( self );

  function startFeedsAndArticles( id, doc){
    if ( ! doc || ! doc.feedList || doc.feedList.length === 0 )
      return;
    var feedCursor = Feeds.find( {_id:{ $in: doc.feedList}}, feedOptions);
    feedPublisher.start( feedCursor );
    var articleCursor = Articles.find( {feed_id: {$in: doc.feedList}, date: {$gt: keepLimitDate()}}, articleOptions);
    articlePublisher.start( articleCursor );
  }

  userObserver = Meteor.users.find( userId, {fields: {feedList: 1}} ).observeChanges({
    added: startFeedsAndArticles,
    changed: startFeedsAndArticles
  });

  self.onStop( () => {
    userObserver.stop();
    feedPublisher.stop();
    delete self.feedPublisher;
    articlePublisher.stop();
    delete self.articlePublisher;
  });

  return Meteor.users.find( userId, {fields: {admin: 1}});
});

Meteor.publish( 'articles', function( feed_ids, limit ){
  var self = this;
  check( feed_ids, [String] );
  check( limit, Number );
  var startDate = keepLimitDate();
  var visibleFields = {_id: 1, title: 1, source: 1, date: 1, summary: 1, link: 1, feed_id: 1};
  return Articles.find( {feed_id: {$in: feed_ids}, date: {$gt: startDate}}, {fields: visibleFields, limit: limit, sort: {date: -1, _id: 1}});
});

Meteor.startup( () => {

  Meteor.call('findArticles', {} );
  Meteor.call('removeOldArticles');

  intervalProcesses["removeOldArticles"] = Meteor.setInterval(
    () => Meteor.call('removeOldArticles'),
    DAY
  );

  intervalProcesses[ "findArticles"] = Meteor.setInterval(
    () => Meteor.call('findArticles', { hub: null} ),
    updateInterval
  );

  var options = {
    callbackUrl: Meteor.absoluteUrl("hubbub"),
    secret: Random.id()
  };

  feedSubscriber = new FeedSubscriber ( options );

  feedSubscriber.on( 'feedStream',
    Meteor.bindEnvironment( function( stream, topic ) {
      var feed = feedSubscriber.subscriptions[ topic ];
      FeedParser.readAndInsertArticles( stream, feed )
    }, ( error ) => console.log( error ) )
  );

  process.on('exit', Meteor.bindEnvironment ( function (){
    feedSubscriber.stopAllSubscriptions();
    console.log( "paused to allow subscriptions to end");

  }, function ( e ) { throw e; }));

  _.each(['SIGINT', 'SIGHUP', 'SIGTERM'], function (sig) {
    process.once(sig, Meteor.bindEnvironment (function () {
      console.log ( "process received : " + sig);
      feedSubscriber.stopAllSubscriptions();
      process.kill( process.pid, sig);
    }, function ( e ) { throw e; }));
  });

  var handle = Feeds.find({hub: {$ne: null}},{fields: {_id: 1, hub:1, url:1}}).observeChanges({

    added: function ( id, fields ){
      fields._id = id;
      feedSubscriber.subscriptions[ fields.url ] = fields;
      feedSubscriber.subscribe ( fields.url, fields.hub );
    },

    removed: function( id ){
      var fields = _( feedSubscriber.subscriptions ).findWhere( {_id: id});
      fields.unsub = new Future();
      feedSubscriber.unsubscribe( fields.url, fields.hub );
    },

    changed: function ( id, fields ){
      var oldSub = _( feedSubscriber.subscriptions ).findWhere({_id :id });
      fields = _( fields ).default( oldSub );
      oldSub.unsub = new Future();
	    feedSubscriber.unsubscribe( oldSub.url, oldSub.hub );
      oldSub.unsub.wait();
      feedSubscriber.subscriptions[ fields.url ] = fields;
	    feedSubscriber.subscribe ( fields.url, fields.hub );
    }
  });
});

Meteor.methods({

  findArticles: function( criteria ) {
    check ( criteria,  Object );
    console.time("findArticles");
    criteria = criteria || {};
    var article_count = 0;
    var feeds = Feeds.find( criteria );
    if ( feeds.count() < 1) return;
    var rssResults = FeedParser.syncFP( feeds.fetch() );

    rssResults.forEach( function( rssResult ){
      if ( rssResult.statusCode === 200 ) {
        Feeds.update(rssResult._id, {$set: _( rssResult ).pick( 'lastModified', 'etag', 'lastDate' ) } );
      }
      else if ( rssResult.error ) console.log (rssResult.url + " returned " + rssResult.error);
      else if ( typeof rssResult.statusCode === "number" && rssResult.statusCode !== 304 ){
        console.log( rssResult.url + " responded with " + rssResult.statusCode );
      }
    });

    console.timeEnd("findArticles");

  },

  stopAndRestartPubSub: function(){
    feedSubscriber.stopAllSubscriptions();

    var onErr = function(err, res){ if( err ) console.error( err );};
    var onFeed = function( feed ){
      feedSubscriber.subscriptions[ feed.url ] = feed;
      feedSubscriber.subscribe( feed.url, feed.hub, onErr );
    };

    Feeds.find({hub: {$ne: null}},{fields: {_id: 1, hub:1, url:1}}).forEach( onFeed );
  },

  removeOldArticles: function(){
    console.log("removeOldArticles method called on server");
    var error = Articles.remove({date:  {$lt: keepLimitDate()}, clicks: 0 }, function(error){ return error;});
    return error || 'success';
  },

  addSubscriberToFeeds: function(){
    var self = this;
    Feeds.find({}).forEach( function ( feed ){
      console.log("adding subscriber " + self.userId);
      Feeds.update( feed._id,{ $addToSet: { subscribers: self.userId }});
    });
  },

  createNullSubscriber: function(){
    var feedList = Feeds.find( {subscribers: null}, {fields: {_id:1}}).map( feed => feed._id );
    Meteor.users.upsert( 'nullUser', { _id: 'nullUser', feedList }, error => {
      if ( error ) console.log( error );
    });
  },

  addFeed_idToArticles: function(){
    Articles.find({}).forEach( function (article){
      var feed_id = Feeds.findOne({title: article.source})._id;
      Articles.update(article._id,{$set: {feed_id: feed_id}});
    });

  },

  cleanUrls: function(){
    Feeds.find({}).forEach( function(feed){
      var result = FeedParser.syncFP( feed );
      if (result && result.url && feed.url !== result.url ){
	      console.log("changing url " + feed.url + " to " + result.url);
	      Feeds.update(feed._id, {$set: {url: result.url }});
      }
    });
  },

  markRead: function( link ){
    var self = this;
    check( link, String );
    var article = Articles.findOne({link: link});
    if ( article ){
      Articles.update( article._id,{$addToSet: {readBy: this.userId }, $inc: {clicks: 1, readCount: 1}}, function( error, result){
	//make update async since client might be waiting to navigate
      });
    }
    console.log( "marked as read: " + link);
  },

  XML2JSparse: function ( file ) {
    check( file, String);
    return XML2JS.parse( file );
  },

  createFeedListByUser: function(){
    Meteor.users.find( {}, {_id:1} ).forEach( function ( user ){
      var feedList = Feeds.find( {subscribers: user._id}, {_id: 1} ).map( function( feed ) { return feed._id });
      Meteor.users.update( user._id, {$set: {feedList: feedList}, $pull:{feedList: null}});
    //  Meteor.users.update( user._id, { $pull:{feedList: null}});
    });
  },

  checkAdmin: function(){
    var user = Meteor.users.findOne( {_id: this.userId} );
    return user && user.admin;
  }

});
<|endoftext|>


<commit_before>module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    compass: {
      dist: {
        options: {
          config: 'config.rb',
          force: true
        }
      }
    },
    watch: {
      dist: {
        files: 'scss/**',
        tasks: ['compass'],
        options: {
          livereload: true
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-compass');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.registerTask('default', ['watch']);
};<commit_msg>LiveReload port changed to 1337<commit_after>module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    compass: {
      dist: {
        options: {
          config: 'config.rb',
          force: true
        }
      }
    },
    watch: {
      dist: {
        files: 'scss/**',
        tasks: ['compass'],
        options: {
          livereload: 1337
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-compass');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.registerTask('default', ['watch']);
};<|endoftext|>


<commit_before>module.exports = function( grunt ) {

  grunt.config.set('files', [ 'lib/**/*.js' ]);

  // Project configuration
  grunt.config.init({

    // Meta data from config file
    pkg: grunt.file.readJSON('package.json'),

    // Linting options
    jshint:
    {
      // Defaults
      options: grunt.file.readJSON('.jshintrc'),
      // Files to watch
      files: { src: grunt.config.get('files') },

      tests: { src: ['test/**/*.js'] }
    },

    // Watch tasks to run on file changes
    watch:
    {
      files: grunt.config.get('files'),
      tasks: ['jshint:files', 'uglify'],

      // To run tests on a watch, simply:
      // $ grunt watch:test
      test: {
        files: ['test/**/*.js'].concat( grunt.config.get('files') ),
        tasks: ['jshint', 'mochaTest']
      }
    },

    component_build: {
      query: {
        output: './build',
        scripts: true,
        styles: false,
        standalone: true
      }
    },

    uglify: {
      build: {
        options: {
          sourceMap: 'build/query.source.map',
          report: 'min',
          mangle: { except:['Query'] },
          banner: '/* <%= pkg.name %> v<%= pkg.version %> */\n'
        },
        files: { 'build/query.min.js': ['build/query.js'] }
      }
    },

    mochaTest: {
      test: {
        options: {
          reporter: 'spec'
        },
        src: ['test/runner.js']
      },
      // Test coverage
      coverage: {
        options: {
          reporter: 'html-cov',
          quiet: true,
          captureFile: 'coverage.html'
        },
        src: ['test/runner.js']
      }
    }

  });

  // Load plugins
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-component-build');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-mocha-test');

  // Default task(s)
  grunt.registerTask('default', ['jshint', 'component_build', 'uglify', 'mochaTest:test']);
  grunt.registerTask('lint', ['jshint']);
  grunt.registerTask('test', ['mochaTest:test']);
  grunt.registerTask('cover', ['mochaTest:coverage']);

};
<commit_msg>Separate 'build' task for Grunt<commit_after>module.exports = function( grunt ) {

  grunt.config.set('files', [ 'lib/**/*.js' ]);

  // Project configuration
  grunt.config.init({

    // Meta data from config file
    pkg: grunt.file.readJSON('package.json'),

    // Linting options
    jshint:
    {
      // Defaults
      options: grunt.file.readJSON('.jshintrc'),
      // Files to watch
      files: { src: grunt.config.get('files') },

      tests: { src: ['test/**/*.js'] }
    },

    // Watch tasks to run on file changes
    watch:
    {
      files: grunt.config.get('files'),
      tasks: ['jshint:files', 'uglify'],

      // To run tests on a watch, simply:
      // $ grunt watch:test
      test: {
        files: ['test/**/*.js'].concat( grunt.config.get('files') ),
        tasks: ['jshint', 'mochaTest']
      }
    },

    component_build: {
      query: {
        output: './build',
        scripts: true,
        styles: false,
        standalone: true
      }
    },

    uglify: {
      build: {
        options: {
          sourceMap: 'build/query.source.map',
          report: 'min',
          mangle: { except:['Query'] },
          banner: '/* <%= pkg.name %> v<%= pkg.version %> */\n'
        },
        files: { 'build/query.min.js': ['build/query.js'] }
      }
    },

    mochaTest: {
      test: {
        options: {
          reporter: 'spec'
        },
        src: ['test/runner.js']
      },
      // Test coverage
      coverage: {
        options: {
          reporter: 'html-cov',
          quiet: true,
          captureFile: 'coverage.html'
        },
        src: ['test/runner.js']
      }
    }

  });

  // Load plugins
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-component-build');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-mocha-test');

  // Default task(s)
  grunt.registerTask('default', ['jshint', 'component_build', 'uglify', 'mochaTest:test']);
  grunt.registerTask('lint', ['jshint']);
  grunt.registerTask('test', ['mochaTest:test']);
  grunt.registerTask('cover', ['mochaTest:coverage']);
  grunt.registerTask('build', ['component_build', 'uglify']);

};
<|endoftext|>


<commit_before>/**
 * Rich Text Editor
 */
'use strict';

(function (document, CKEDITOR) {
    const cfg = {
        customConfig: '',
        disableNativeSpellChecker: true,
        extraAllowedContent: 'article section(*)',
        format_tags: 'p;h2;h3',
        height: '30rem',
        language: 'de',
        mediabrowserUrl: '/file/browser',
        removeDialogTabs: 'link:advanced;link:target',
        stylesSet: false,
        toolbar: [
            {
                name: 'all',
                items: [
                    'Undo',
                    'Redo',
                    'Bold',
                    'Italic',
                    'Link',
                    'Unlink',
                    'Format',
                    'BulletedList',
                    'NumberedList',
                    'Blockquote',
                    'Media',
                    'Table',
                    'Detail'
                ]
            }
        ]
    };

    document.addEventListener('DOMContentLoaded', function () {
        Array.prototype.forEach.call(document.querySelectorAll('textarea[data-type=rte]'), function (item) {
            CKEDITOR.replace(item, cfg);
        });
    });
})(document, CKEDITOR);
<commit_msg>editor config<commit_after>/**
 * Rich Text Editor
 */
'use strict';

(function (document, CKEDITOR) {
    const cfg = {
        customConfig: '',
        disableNativeSpellChecker: true,
        extraAllowedContent: 'article section(*)',
        fillEmptyBlocks: false,
        format_tags: 'p;h2;h3',
        height: '30rem',
        language: 'de',
        mediabrowserUrl: '/file/browser',
        removeDialogTabs: 'link:advanced;link:target',
        stylesSet: false,
        toolbar: [
            {
                name: 'all',
                items: [
                    'Undo',
                    'Redo',
                    'Bold',
                    'Italic',
                    'Link',
                    'Unlink',
                    'Format',
                    'BulletedList',
                    'NumberedList',
                    'Blockquote',
                    'Media',
                    'Table',
                    'Detail'
                ]
            }
        ]
    };

    document.addEventListener('DOMContentLoaded', function () {
        Array.prototype.forEach.call(document.querySelectorAll('textarea[data-type=rte]'), function (item) {
            CKEDITOR.replace(item, cfg);
        });
    });
})(document, CKEDITOR);
<|endoftext|>


<commit_before>/* global describe, afterEach, it, expect */

var expect = require('chai').expect;  // jshint ignore:line
var concat = require('..');
var RSVP = require('rsvp');
RSVP.on('error', function(err){throw err;});
var fs = require('fs');
var path = require('path');
var broccoli = require('broccoli');
var merge = require('broccoli-merge-trees');

var fixtures = path.join(__dirname, 'fixtures');
var builder;

describe('sourcemap-concat', function() {
  it('concatenates files in one dir', function() {
    var tree = concat(fixtures, {
      outputFile: '/all-inner.js',
      inputFiles: ['inner/*.js']
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('all-inner.js').in(result);
      expectFile('all-inner.map').in(result);
    });
  });

  it('concatenates files across dirs', function() {
    var tree = concat(fixtures, {
      outputFile: '/all.js',
      inputFiles: ['**/*.js']
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('all.js').in(result);
      expectFile('all.map').in(result);
    });
  });

  it('inserts header', function() {
    var tree = concat(fixtures, {
      outputFile: '/all-with-header.js',
      inputFiles: ['**/*.js'],
      header: "/* This is my header. */"
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('all-with-header.js').in(result);
      expectFile('all-with-header.map').in(result);
    });
  });

  it('inserts header when sourcemaps are disabled', function() {
    var tree = concat(fixtures, {
      outputFile: '/all-with-header.js',
      inputFiles: ['**/*.js'],
      header: "/* This is my header. */",
      sourceMapConfig: { enabled: false }
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('all-with-header.js').withoutSrcURL().in(result);
      expectFile('all-with-header.map').notIn(result);
    });
  });

  it('disables sourcemaps when requested', function() {
    var tree = concat(fixtures, {
      outputFile: '/no-sourcemap.js',
      inputFiles: ['**/*.js'],
      header: "/* This is my header. */",
      sourceMapConfig: { extensions: [] }
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('no-sourcemap.js').in(result);
      expectFile('no-sourcemap.map').notIn(result);
    });
  });

  it('assimilates existing sourcemap', function() {
    var inner = concat(fixtures, {
      outputFile: '/all-inner.js',
      inputFiles: ['inner/*.js'],
      header: "/* This is my header. */"
    });
    var other = concat(fixtures, {
      outputFile: '/all-other.js',
      inputFiles: ['other/*.js'],
      header: "/* Other header. */"
    });
    var final = concat(merge([inner, other]), {
      outputFile: '/staged.js',
      inputFiles: ['all-inner.js', 'all-other.js'],
    });

    builder = new broccoli.Builder(final);
    return builder.build().then(function(result) {
      expectFile('staged.js').in(result);
      expectFile('staged.map').in(result);
    });
  });

  it('appends footer files', function() {
    var tree = concat(fixtures, {
      outputFile: '/inner-with-footers.js',
      inputFiles: ['inner/*.js'],
      footerFiles: ['other/third.js', 'other/fourth.js']
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('inner-with-footers.js').in(result);
      expectFile('inner-with-footers.map').in(result);
    });
  });

  it('appends footer files when sourcemaps are disabled', function() {
    var tree = concat(fixtures, {
      outputFile: '/inner-with-footers.js',
      inputFiles: ['inner/*.js'],
      footerFiles: ['other/third.js', 'other/fourth.js'],
      sourceMapConfig: { extensions: [] }
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('inner-with-footers.js').withoutSrcURL().in(result);
      expectFile('inner-with-footers.map').notIn(result);
    });
  });

  it('can ingore empty content', function() {
    var tree = concat(fixtures, {
      outputFile: '/nothing.js',
      inputFiles: ['nothing/*.jsa'],
      allowNone: true
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('nothing.js').in(result);
      expectFile('nothing.map').in(result);
    });
  });

  it('can ingore empty content when sourcemaps are disabled', function() {
    var tree = concat(fixtures, {
      outputFile: '/nothing.css',
      inputFiles: ['nothing/*.css'],
      allowNone: true
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('nothing.css').in(result);
    });
  });

  afterEach(function() {
    if (builder) {
      return builder.cleanup();
    }
  });

});

function expectFile(filename) {
  var stripURL = false;
  return {
      in: function(result, subdir) {
        if (!subdir) {
          subdir = '.';
        }
        var actualContent = fs.readFileSync(path.join(result.directory, subdir, filename), 'utf-8');
        fs.writeFileSync(path.join(__dirname, 'actual', filename), actualContent);

        var expectedContent;
        try {
          expectedContent = fs.readFileSync(path.join(__dirname, 'expected', filename), 'utf-8');
          if (stripURL) {
            expectedContent = expectedContent.replace(/\/\/# sourceMappingURL=.*$/, '');
          }

        } catch (err) {
          console.warn("Missing expcted file: " + path.join(__dirname, 'expected', filename));
        }
        expectSameFiles(actualContent, expectedContent, filename);
        return this;
      },
    notIn: function(result) {
      expect(fs.existsSync(path.join(result.directory, filename))).to.equal(false, filename + ' should not have been present');
      return this;
    },
    withoutSrcURL: function() {
      stripURL = true;
      return this;
    }
  };
}

function expectSameFiles(actualContent, expectedContent, filename) {
  if (/\.map$/.test(filename)) {
    expect(JSON.parse(actualContent)).to.deep.equal(expectedContent ? JSON.parse(expectedContent) : undefined, 'discrepancy in ' + filename);
  } else {
    expect(actualContent).to.equal(expectedContent, 'discrepancy in ' + filename);
  }
}
<commit_msg>drop unintended stray character<commit_after>/* global describe, afterEach, it, expect */

var expect = require('chai').expect;  // jshint ignore:line
var concat = require('..');
var RSVP = require('rsvp');
RSVP.on('error', function(err){throw err;});
var fs = require('fs');
var path = require('path');
var broccoli = require('broccoli');
var merge = require('broccoli-merge-trees');

var fixtures = path.join(__dirname, 'fixtures');
var builder;

describe('sourcemap-concat', function() {
  it('concatenates files in one dir', function() {
    var tree = concat(fixtures, {
      outputFile: '/all-inner.js',
      inputFiles: ['inner/*.js']
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('all-inner.js').in(result);
      expectFile('all-inner.map').in(result);
    });
  });

  it('concatenates files across dirs', function() {
    var tree = concat(fixtures, {
      outputFile: '/all.js',
      inputFiles: ['**/*.js']
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('all.js').in(result);
      expectFile('all.map').in(result);
    });
  });

  it('inserts header', function() {
    var tree = concat(fixtures, {
      outputFile: '/all-with-header.js',
      inputFiles: ['**/*.js'],
      header: "/* This is my header. */"
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('all-with-header.js').in(result);
      expectFile('all-with-header.map').in(result);
    });
  });

  it('inserts header when sourcemaps are disabled', function() {
    var tree = concat(fixtures, {
      outputFile: '/all-with-header.js',
      inputFiles: ['**/*.js'],
      header: "/* This is my header. */",
      sourceMapConfig: { enabled: false }
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('all-with-header.js').withoutSrcURL().in(result);
      expectFile('all-with-header.map').notIn(result);
    });
  });

  it('disables sourcemaps when requested', function() {
    var tree = concat(fixtures, {
      outputFile: '/no-sourcemap.js',
      inputFiles: ['**/*.js'],
      header: "/* This is my header. */",
      sourceMapConfig: { extensions: [] }
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('no-sourcemap.js').in(result);
      expectFile('no-sourcemap.map').notIn(result);
    });
  });

  it('assimilates existing sourcemap', function() {
    var inner = concat(fixtures, {
      outputFile: '/all-inner.js',
      inputFiles: ['inner/*.js'],
      header: "/* This is my header. */"
    });
    var other = concat(fixtures, {
      outputFile: '/all-other.js',
      inputFiles: ['other/*.js'],
      header: "/* Other header. */"
    });
    var final = concat(merge([inner, other]), {
      outputFile: '/staged.js',
      inputFiles: ['all-inner.js', 'all-other.js'],
    });

    builder = new broccoli.Builder(final);
    return builder.build().then(function(result) {
      expectFile('staged.js').in(result);
      expectFile('staged.map').in(result);
    });
  });

  it('appends footer files', function() {
    var tree = concat(fixtures, {
      outputFile: '/inner-with-footers.js',
      inputFiles: ['inner/*.js'],
      footerFiles: ['other/third.js', 'other/fourth.js']
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('inner-with-footers.js').in(result);
      expectFile('inner-with-footers.map').in(result);
    });
  });

  it('appends footer files when sourcemaps are disabled', function() {
    var tree = concat(fixtures, {
      outputFile: '/inner-with-footers.js',
      inputFiles: ['inner/*.js'],
      footerFiles: ['other/third.js', 'other/fourth.js'],
      sourceMapConfig: { extensions: [] }
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('inner-with-footers.js').withoutSrcURL().in(result);
      expectFile('inner-with-footers.map').notIn(result);
    });
  });

  it('can ingore empty content', function() {
    var tree = concat(fixtures, {
      outputFile: '/nothing.js',
      inputFiles: ['nothing/*.js'],
      allowNone: true
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('nothing.js').in(result);
      expectFile('nothing.map').in(result);
    });
  });

  it('can ingore empty content when sourcemaps are disabled', function() {
    var tree = concat(fixtures, {
      outputFile: '/nothing.css',
      inputFiles: ['nothing/*.css'],
      allowNone: true
    });
    builder = new broccoli.Builder(tree);
    return builder.build().then(function(result) {
      expectFile('nothing.css').in(result);
    });
  });

  afterEach(function() {
    if (builder) {
      return builder.cleanup();
    }
  });

});

function expectFile(filename) {
  var stripURL = false;
  return {
      in: function(result, subdir) {
        if (!subdir) {
          subdir = '.';
        }
        var actualContent = fs.readFileSync(path.join(result.directory, subdir, filename), 'utf-8');
        fs.writeFileSync(path.join(__dirname, 'actual', filename), actualContent);

        var expectedContent;
        try {
          expectedContent = fs.readFileSync(path.join(__dirname, 'expected', filename), 'utf-8');
          if (stripURL) {
            expectedContent = expectedContent.replace(/\/\/# sourceMappingURL=.*$/, '');
          }

        } catch (err) {
          console.warn("Missing expcted file: " + path.join(__dirname, 'expected', filename));
        }
        expectSameFiles(actualContent, expectedContent, filename);
        return this;
      },
    notIn: function(result) {
      expect(fs.existsSync(path.join(result.directory, filename))).to.equal(false, filename + ' should not have been present');
      return this;
    },
    withoutSrcURL: function() {
      stripURL = true;
      return this;
    }
  };
}

function expectSameFiles(actualContent, expectedContent, filename) {
  if (/\.map$/.test(filename)) {
    expect(JSON.parse(actualContent)).to.deep.equal(expectedContent ? JSON.parse(expectedContent) : undefined, 'discrepancy in ' + filename);
  } else {
    expect(actualContent).to.equal(expectedContent, 'discrepancy in ' + filename);
  }
}
<|endoftext|>


<commit_before>import WebGLSystem from '../WebGLSystem';
import { Rectangle, Matrix } from '../../../../math';
import VertexArrayObject from './VertexArrayObject';
import GLBuffer from './GLBuffer';
import setVertexAttribArrays from './setVertexAttribArrays';

const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 };

/**
 * @class
 * @extends PIXI.WebGLSystem
 * @memberof PIXI
 */

export default class GeometrySystem extends WebGLSystem
{
    /**
     * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for.
     */
    constructor(renderer)
    {
        super(renderer);

        this._activeGeometry = null;
        this._activeVao = null;

        this.hasVao = true;
    }

    /**
     * Sets up the renderer context and necessary buffers.
     *
     * @private
     */
    contextChange()
    {
        const gl = this.gl = this.renderer.gl;
        this.CONTEXT_UID = this.renderer.CONTEXT_UID;

        // webgl2
        if(!gl.createVertexArray)
        {
            // webgl 1!
            const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') ||
                                  gl.getExtension('MOZ_OES_vertex_array_object') ||
                                  gl.getExtension('WEBKIT_OES_vertex_array_object');

            if(nativeVaoExtension)
            {
                gl.createVertexArray = nativeVaoExtension.createVertexArrayOES;
                gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES;
                gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES;
            }
            else
            {
                this.hasVao = false;
                gl.createVertexArray = ()=>{return {}}
                gl.bindVertexArray = ()=>{}
                gl.deleteVertexArray = ()=>{}
            }
        }
    }

    /**
     * Binds geometry so that is can be drawn. Creating a Vao if required
     * @private
     * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind
     */
    bind(geometry, shader)
    {
        shader = shader || this.renderer.shader.shader;

        const gl = this.gl;

        // not sure the best way to address this..
        // currently different shaders require different VAOs for the same geometry
        // Still mulling over the best way to solve this one..
        // will likely need to modify the shader attribute locations at run time!
        let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID];

        if(!vaos)
        {
            vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {};
        }

        const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program);

        this._activeGeometry = geometry;

        if(this._activeVao !== vao)
        {
            this._activeVao = vao;

            if(this.hasVao)
            {
                gl.bindVertexArray(vao);
            }
            else
            {
                this.activateVao(geometry, shader.program);
            }
        }

        // TODO - optimise later!
        // don't need to loop through if nothing changed!
        // maybe look to add an 'autoupdate' to geometry?
        this.updateBuffers();
    }

    updateBuffers()
    {
        const geometry = this._activeGeometry;
        const gl = this.gl;

        for (let i = 0; i < geometry.buffers.length; i++)
        {
            const buffer = geometry.buffers[i];

            const glBuffer = buffer._glBuffers[this.CONTEXT_UID];

            if (buffer._updateID !== glBuffer._updateID)
            {
                glBuffer._updateID = buffer._updateID;

                // TODO can cache this on buffer! maybe added a getter / setter?
                const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
                const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW;

                gl.bindBuffer(type, glBuffer.buffer);

                if(glBuffer.byteLength >= buffer.data.byteLength)
                {
                    // offset is always zero for now!
                    gl.bufferSubData(type, 0, buffer.data);
                }
                else
                {
                    gl.bufferData(type, buffer.data, drawType);
                }

            }
        }
    }

    checkCompatability(geometry, program)
    {
        // geometry must have at least all the attributes that the shader requires.
        const geometryAttributes = geometry.attributes;
        const shaderAttributes = program.attributeData;

        for (const j in shaderAttributes)
        {
            if(!geometryAttributes[j])
            {
                throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute');
            }
        }
    }

    /**
     * Creates a Vao with the same structure as the geometry and stores it on the geometry.
     * @private
     * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for
     * @return {PIXI.VertexArrayObject} Returns a fresh vao.
     */
    initGeometryVao(geometry, program)
    {
        this.checkCompatability(geometry, program);

        const gl = this.gl;
        const CONTEXT_UID = this.CONTEXT_UID;
        const buffers = geometry.buffers;
        const attributes = geometry.attributes;

        // first update - and create the buffers!
        for (let i = 0; i < buffers.length; i++)
        {
            const buffer = buffers[i];

            if (!buffer._glBuffers[CONTEXT_UID])
            {
                buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer());
            }
        }

        const tempStride = {};
        const tempStart = {};

        for (const j in buffers)
        {
            tempStride[j] = 0;
            tempStart[j] = 0;
        }

        for (const j in attributes)
        {
            if(!attributes[j].size && program.attributeData[j])
            {
                attributes[j].size = program.attributeData[j].size;
            }

            tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type];
        }

        for (const j in attributes)
        {
            const attribute = attributes[j];
            const attribSize = attribute.size;

            // must be careful that the geometry has attributes not used by this shader..

            if (attribute.stride === undefined)
            {
                if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type])
                {
                    attribute.stride = 0;
                }
                else
                {
                    attribute.stride = tempStride[attribute.buffer];
                }
            }

            if (attribute.start === undefined)
            {
                attribute.start = tempStart[attribute.buffer];

                tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type];
            }
        }

        // TODO - maybe make this a data object?
        // lets wait to see if we need to first!
        const vao = gl.createVertexArray();

        gl.bindVertexArray(vao);

        this.activateVao(geometry, program);

        gl.bindVertexArray(null);

        geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao;

    }

    activateVao(geometry, program)
    {
        const gl = this.gl;
        const CONTEXT_UID = this.CONTEXT_UID;
        const buffers = geometry.buffers;
        const attributes = geometry.attributes;

        if (geometry.indexBuffer)
        {
            // first update the index buffer if we have one..
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer);
        }

        let lastBuffer = null;

        // add a new one!
        for (const j in attributes)
        {
            const attribute = attributes[j];
            const buffer = buffers[attribute.buffer];
            const glBuffer = buffer._glBuffers[CONTEXT_UID];

            if(program.attributeData[j])
            {
                if(lastBuffer !== glBuffer)
                {
                    gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer);

                    lastBuffer = glBuffer;
                }

                const location = program.attributeData[j].location;

                //TODO introduce state again
                //we can optimise this for older devices that have no VAOs
                gl.enableVertexAttribArray(location);

                gl.vertexAttribPointer(location,
                                       attribute.size,
                                       attribute.type || gl.FLOAT,
                                       attribute.normalized,
                                       attribute.stride,
                                       attribute.start);

                if(attribute.instance)
                {
                    gl.vertexAttribDivisor(location, 1);
                }
            }
        }
    }

    draw(type, size, start, instanceCount)
    {
        const gl = this.gl;
        const geometry = this._activeGeometry;

        //TODO.. this should not change so maybe cache the function?

        if(geometry.indexBuffer)
        {
            if(geometry.instanced)
            {
                gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1);
            }
            else
            {
                gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 );
            }
        }
        else
        {
            if(geometry.instanced)
            {
                // TODO need a better way to calculate size..
                gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1);
            }
            else
            {
                gl.drawArrays(type, start, size || geometry.getSize());
            }
        }

        return this;
    }

    /**
     * Creates a new VAO from this renderer's context and state.
     *
     * @return {VertexArrayObject} The new VAO.
     */
    createVao()
    {
        return new VertexArrayObject(this.gl, this.renderer.state.attribState);
    }

    /**
     * Changes the current Vao to the one given in parameter
     *
     * @param {PIXI.VertexArrayObject} vao - the new Vao
     * @return {PIXI.WebGLRenderer} Returns itself.
     */
    bindVao(vao)
    {
    }
}

class GLBufferData
{
    constructor(buffer)
    {
        this.buffer = buffer;
        this.updateID = -1;
        this.byteLength = -1;
    }
}
<commit_msg>Added fallback for instancing<commit_after>import WebGLSystem from '../WebGLSystem';
import { Rectangle, Matrix } from '../../../../math';
import VertexArrayObject from './VertexArrayObject';
import GLBuffer from './GLBuffer';
import setVertexAttribArrays from './setVertexAttribArrays';

const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 };

/**
 * @class
 * @extends PIXI.WebGLSystem
 * @memberof PIXI
 */

export default class GeometrySystem extends WebGLSystem
{
    /**
     * @param {PIXI.WebGLRenderer} renderer - The renderer this System works for.
     */
    constructor(renderer)
    {
        super(renderer);

        this._activeGeometry = null;
        this._activeVao = null;

        this.hasVao = true;
        this.hasInstance = true;
    }

    /**
     * Sets up the renderer context and necessary buffers.
     *
     * @private
     */
    contextChange()
    {
        const gl = this.gl = this.renderer.gl;
        this.CONTEXT_UID = this.renderer.CONTEXT_UID;

        // webgl2
        if(!gl.createVertexArray)
        {
            // webgl 1!
            const nativeVaoExtension = gl.getExtension('OES_vertex_array_object') ||
                                  gl.getExtension('MOZ_OES_vertex_array_object') ||
                                  gl.getExtension('WEBKIT_OES_vertex_array_object');

            if(nativeVaoExtension)
            {
                gl.createVertexArray = nativeVaoExtension.createVertexArrayOES;
                gl.bindVertexArray = nativeVaoExtension.bindVertexArrayOES;
                gl.deleteVertexArray = nativeVaoExtension.deleteVertexArrayOES;
            }
            else
            {
                this.hasVao = false;
                gl.createVertexArray = ()=>{return {}}
                gl.bindVertexArray = ()=>{}
                gl.deleteVertexArray = ()=>{}
            }
        }

        if(!gl.vertexAttribDivisor)
        {
            const instanceExt = gl.getExtension("ANGLE_instanced_arrays");

            if(instanceExt)
            {
                gl.vertexAttribDivisor = instanceExt.vertexAttribDivisorANGLE;
                gl.drawElementsInstanced = instanceExt.drawElementsInstancedANGLE;
                gl.drawArrayInstanced = instanceExt.drawArraysInstancedANGLE;
            }
        }

    }

    /**
     * Binds geometry so that is can be drawn. Creating a Vao if required
     * @private
     * @param {PIXI.mesh.Geometry} geometry instance of geometry to bind
     */
    bind(geometry, shader)
    {
        shader = shader || this.renderer.shader.shader;

        const gl = this.gl;

        // not sure the best way to address this..
        // currently different shaders require different VAOs for the same geometry
        // Still mulling over the best way to solve this one..
        // will likely need to modify the shader attribute locations at run time!
        let vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID];

        if(!vaos)
        {
            vaos = geometry.glVertexArrayObjects[this.CONTEXT_UID] = {};
        }

        const vao = vaos[shader.program.id] || this.initGeometryVao(geometry, shader.program);

        this._activeGeometry = geometry;

        if(this._activeVao !== vao)
        {
            this._activeVao = vao;

            if(this.hasVao)
            {
                gl.bindVertexArray(vao);
            }
            else
            {
                this.activateVao(geometry, shader.program);
            }
        }

        // TODO - optimise later!
        // don't need to loop through if nothing changed!
        // maybe look to add an 'autoupdate' to geometry?
        this.updateBuffers();
    }

    updateBuffers()
    {
        const geometry = this._activeGeometry;
        const gl = this.gl;

        for (let i = 0; i < geometry.buffers.length; i++)
        {
            const buffer = geometry.buffers[i];

            const glBuffer = buffer._glBuffers[this.CONTEXT_UID];

            if (buffer._updateID !== glBuffer._updateID)
            {
                glBuffer._updateID = buffer._updateID;

                // TODO can cache this on buffer! maybe added a getter / setter?
                const type = buffer.index ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
                const drawType = buffer.static ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW;

                gl.bindBuffer(type, glBuffer.buffer);

                if(glBuffer.byteLength >= buffer.data.byteLength)
                {
                    // offset is always zero for now!
                    gl.bufferSubData(type, 0, buffer.data);
                }
                else
                {
                    gl.bufferData(type, buffer.data, drawType);
                }

            }
        }
    }

    checkCompatability(geometry, program)
    {
        // geometry must have at least all the attributes that the shader requires.
        const geometryAttributes = geometry.attributes;
        const shaderAttributes = program.attributeData;

        for (const j in shaderAttributes)
        {
            if(!geometryAttributes[j])
            {
                throw new Error('shader and geometry incompatible, geometry missing the "' + j + '" attribute');
            }
        }
    }

    /**
     * Creates a Vao with the same structure as the geometry and stores it on the geometry.
     * @private
     * @param {PIXI.mesh.Geometry} geometry instance of geometry to to generate Vao for
     * @return {PIXI.VertexArrayObject} Returns a fresh vao.
     */
    initGeometryVao(geometry, program)
    {
        this.checkCompatability(geometry, program);

        const gl = this.gl;
        const CONTEXT_UID = this.CONTEXT_UID;
        const buffers = geometry.buffers;
        const attributes = geometry.attributes;

        // first update - and create the buffers!
        for (let i = 0; i < buffers.length; i++)
        {
            const buffer = buffers[i];

            if (!buffer._glBuffers[CONTEXT_UID])
            {
                buffer._glBuffers[CONTEXT_UID] = new GLBufferData(gl.createBuffer());
            }
        }

        const tempStride = {};
        const tempStart = {};

        for (const j in buffers)
        {
            tempStride[j] = 0;
            tempStart[j] = 0;
        }

        for (const j in attributes)
        {
            if(!attributes[j].size && program.attributeData[j])
            {
                attributes[j].size = program.attributeData[j].size;
            }

            tempStride[attributes[j].buffer] += attributes[j].size * byteSizeMap[attributes[j].type];
        }

        for (const j in attributes)
        {
            const attribute = attributes[j];
            const attribSize = attribute.size;

            // must be careful that the geometry has attributes not used by this shader..

            if (attribute.stride === undefined)
            {
                if (tempStride[attribute.buffer] === attribSize * byteSizeMap[attribute.type])
                {
                    attribute.stride = 0;
                }
                else
                {
                    attribute.stride = tempStride[attribute.buffer];
                }
            }

            if (attribute.start === undefined)
            {
                attribute.start = tempStart[attribute.buffer];

                tempStart[attribute.buffer] += attribSize * byteSizeMap[attribute.type];
            }
        }

        // TODO - maybe make this a data object?
        // lets wait to see if we need to first!
        const vao = gl.createVertexArray();

        gl.bindVertexArray(vao);

        this.activateVao(geometry, program);

        gl.bindVertexArray(null);

        geometry.glVertexArrayObjects[CONTEXT_UID][program.id] = vao;

    }

    activateVao(geometry, program)
    {
        const gl = this.gl;
        const CONTEXT_UID = this.CONTEXT_UID;
        const buffers = geometry.buffers;
        const attributes = geometry.attributes;

        if (geometry.indexBuffer)
        {
            // first update the index buffer if we have one..
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, geometry.indexBuffer._glBuffers[CONTEXT_UID].buffer);
        }

        let lastBuffer = null;

        // add a new one!
        for (const j in attributes)
        {
            const attribute = attributes[j];
            const buffer = buffers[attribute.buffer];
            const glBuffer = buffer._glBuffers[CONTEXT_UID];

            if(program.attributeData[j])
            {
                if(lastBuffer !== glBuffer)
                {
                    gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer.buffer);

                    lastBuffer = glBuffer;
                }

                const location = program.attributeData[j].location;

                //TODO introduce state again
                //we can optimise this for older devices that have no VAOs
                gl.enableVertexAttribArray(location);

                gl.vertexAttribPointer(location,
                                       attribute.size,
                                       attribute.type || gl.FLOAT,
                                       attribute.normalized,
                                       attribute.stride,
                                       attribute.start);

                if(attribute.instance)
                {
                    if(this.hasInstance)
                    {
                        gl.vertexAttribDivisor(location, 1);
                    }
                    else
                    {
                        throw new Error('geometry error, GPU Instancing is not supported on this device');
                    }
                }
            }
        }
    }

    draw(type, size, start, instanceCount)
    {
        const gl = this.gl;
        const geometry = this._activeGeometry;

        //TODO.. this should not change so maybe cache the function?

        if(geometry.indexBuffer)
        {
            if(geometry.instanced)
            {
                gl.drawElementsInstanced(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2, instanceCount || 1);
            }
            else
            {
                gl.drawElements(type, size || geometry.indexBuffer.data.length, gl.UNSIGNED_SHORT, (start || 0) * 2 );
            }
        }
        else
        {
            if(geometry.instanced)
            {
                // TODO need a better way to calculate size..
                gl.drawArrayInstanced(type, start, size || geometry.getSize(), instanceCount || 1);
            }
            else
            {
                gl.drawArrays(type, start, size || geometry.getSize());
            }
        }

        return this;
    }

    /**
     * Creates a new VAO from this renderer's context and state.
     *
     * @return {VertexArrayObject} The new VAO.
     */
    createVao()
    {
        return new VertexArrayObject(this.gl, this.renderer.state.attribState);
    }

    /**
     * Changes the current Vao to the one given in parameter
     *
     * @param {PIXI.VertexArrayObject} vao - the new Vao
     * @return {PIXI.WebGLRenderer} Returns itself.
     */
    bindVao(vao)
    {
    }
}

class GLBufferData
{
    constructor(buffer)
    {
        this.buffer = buffer;
        this.updateID = -1;
        this.byteLength = -1;
    }
}
<|endoftext|>


<commit_before>// Import and configure assertion framework
var chai = require('chai');
chai.use(require('chai-datetime'));
var assert = chai.assert;

// Import the library to test.
var IntervalRecurrence = require('../dist/');

// Describe tests.
describe('iso8601-interval-recurrence', function () {

	it('should be able to check a date against a start date and interval', function () {
		// Matches every Wednesday
		var every_wednesday = new IntervalRecurrence({
			interval: '2015-W01-3T/P1D',
			recurrence: 'R/P1W'
		});

		var wednesday = '2015-07-29T13:00:00.000Z';
		var tuesday = '2015-07-23T11:12:13.000Z';

		assert.equal(every_wednesday.containsDate(wednesday), true);
		assert.equal(every_wednesday.containsDate(tuesday),   false);
	});

	it('should be able to check a date against an interval and end date', function () {
		// Matches every wednesday
		var every_wednesday = new IntervalRecurrence({
			interval: 'P1D/2015-W01-4T',
			recurrence: 'R/P1W'
		});

		var wednesday = '2015-07-29T13:00:00.000Z';
		var tuesday = '2015-07-23T11:12:13.000Z';

		assert.equal(every_wednesday.containsDate(wednesday), true);
		assert.equal(every_wednesday.containsDate(tuesday),   false);
	});

	it('should be able to check only a specific limit of recurrences', function () {
		// Matches every Wednesday for two weeks
		var every_wednesday_for_two_weeks = new IntervalRecurrence({
			interval: '2015-07-22T00:00:00.000Z/P1D',
			recurrence: 'R2/P1W'
		});

		var two_weeks_after = '2015-08-05T00:00:00.000Z';
		var three_weeks_after = '2015-08-12T00:00:00.000Z';

		assert.equal(every_wednesday_for_two_weeks.containsDate(two_weeks_after),   true);
		assert.equal(every_wednesday_for_two_weeks.containsDate(three_weeks_after), false);
	});

	it('should be able to check between to absolute dates', function () {
		// Matches between midnight and midday on a specific day.
		var between_midnight_and_midday = new IntervalRecurrence({
			interval: '2015-07-22T00:00:00.000Z/2015-07-22T12:00:00.000Z',
		});

		var before_midday = '2015-07-22T09:00:00.000Z';
		var after_midday = '2015-07-22T17:00:00.000Z';

		assert.equal(between_midnight_and_midday.containsDate(before_midday), true);
		assert.equal(between_midnight_and_midday.containsDate(after_midday),  false);
	});

	it('should be able to return the range that a current date is in', function () {
		// This is only really useful for repetitions.

		// Matches every weekend.
		var weekend = new IntervalRecurrence({
			interval: '2015-W01-6T/P2D',
			recurrence: 'R/P1W'
		});

		var end_of_a_saturday_in_may = '2015-05-09T23:00:00.000Z';
		var end_of_a_saturday_in_june = '2015-06-06T23:00:00.000Z';
		var not_weekend = '2015-06-05T20:00:00.000Z';

		var weekend_in_may = weekend.currentRange(end_of_a_saturday_in_may);
		assert.equalTime(weekend_in_may.start, new Date('2015-05-09T00:00:00.000Z'));
		assert.equalTime(weekend_in_may.end,   new Date('2015-05-11T00:00:00.000Z'));

		var weekend_in_june = weekend.currentRange(end_of_a_saturday_in_june);
		assert.equalTime(weekend_in_june.start, new Date('2015-06-06T00:00:00.000Z'));
		assert.equalTime(weekend_in_june.end,   new Date('2015-06-08T00:00:00.000Z'));

		var fails = weekend.currentRange(not_weekend);
		assert.equal(fails, false);
	});

});<commit_msg>Give examples of the library taking a JavaScript Date as input instead of text (even though both work).<commit_after>// Import and configure assertion framework
var chai = require('chai');
chai.use(require('chai-datetime'));
var assert = chai.assert;

// Import the library to test.
var IntervalRecurrence = require('../dist/');

// Describe tests.
describe('iso8601-interval-recurrence', function () {

	it('should be able to check a date against a start date and interval', function () {
		// Matches every Wednesday
		var every_wednesday = new IntervalRecurrence({
			interval: '2015-W01-3T/P1D',
			recurrence: 'R/P1W'
		});

		var wednesday = new Date('2015-07-29T13:00:00.000Z');
		var tuesday = new Date('2015-07-23T11:12:13.000Z');

		assert.equal(every_wednesday.containsDate(wednesday), true);
		assert.equal(every_wednesday.containsDate(tuesday),   false);
	});

	it('should be able to check a date against an interval and end date', function () {
		// Matches every wednesday
		var every_wednesday = new IntervalRecurrence({
			interval: 'P1D/2015-W01-4T',
			recurrence: 'R/P1W'
		});

		var wednesday = new Date('2015-07-29T13:00:00.000Z');
		var tuesday = new Date('2015-07-23T11:12:13.000Z');

		assert.equal(every_wednesday.containsDate(wednesday), true);
		assert.equal(every_wednesday.containsDate(tuesday),   false);
	});

	it('should be able to check only a specific limit of recurrences', function () {
		// Matches every Wednesday for two weeks
		var every_wednesday_for_two_weeks = new IntervalRecurrence({
			interval: '2015-07-22T00:00:00.000Z/P1D',
			recurrence: 'R2/P1W'
		});

		var two_weeks_after = new Date('2015-08-05T00:00:00.000Z');
		var three_weeks_after = new Date('2015-08-12T00:00:00.000Z');

		assert.equal(every_wednesday_for_two_weeks.containsDate(two_weeks_after),   true);
		assert.equal(every_wednesday_for_two_weeks.containsDate(three_weeks_after), false);
	});

	it('should be able to check between to absolute dates', function () {
		// Matches between midnight and midday on a specific day.
		var between_midnight_and_midday = new IntervalRecurrence({
			interval: '2015-07-22T00:00:00.000Z/2015-07-22T12:00:00.000Z',
		});

		var before_midday = new Date('2015-07-22T09:00:00.000Z');
		var after_midday = new Date('2015-07-22T17:00:00.000Z');

		assert.equal(between_midnight_and_midday.containsDate(before_midday), true);
		assert.equal(between_midnight_and_midday.containsDate(after_midday),  false);
	});

	it('should be able to return the range that a current date is in', function () {
		// This is only really useful for repetitions.

		// Matches every weekend.
		var weekend = new IntervalRecurrence({
			interval: '2015-W01-6T/P2D',
			recurrence: 'R/P1W'
		});

		var end_of_a_saturday_in_may = new Date('2015-05-09T23:00:00.000Z');
		var end_of_a_saturday_in_june = new Date('2015-06-06T23:00:00.000Z');
		var not_weekend = new Date('2015-06-05T20:00:00.000Z');

		var weekend_in_may = weekend.currentRange(end_of_a_saturday_in_may);
		assert.equalTime(weekend_in_may.start, new Date('2015-05-09T00:00:00.000Z'));
		assert.equalTime(weekend_in_may.end,   new Date('2015-05-11T00:00:00.000Z'));

		var weekend_in_june = weekend.currentRange(end_of_a_saturday_in_june);
		assert.equalTime(weekend_in_june.start, new Date('2015-06-06T00:00:00.000Z'));
		assert.equalTime(weekend_in_june.end,   new Date('2015-06-08T00:00:00.000Z'));

		var fails = weekend.currentRange(not_weekend);
		assert.equal(fails, false);
	});

});<|endoftext|>


<commit_before>'use strict';

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      gruntfile: {
        src: 'Gruntfile.js'
      },
      lib: {
        src: ['lib/**/*.js']
      },
      test: {
        src: ['test/**/*.js']
      },
    },
    simplemocha: {
      options: {
        globals: ['should'],
        uid: 'bdd',
        reporter: 'tap'
      },
      all: {
        src: 'test/**/*-test.js'
      }
    },
    browserify: {
      'dist/bundle.js': {
        requires: ['./lib/viewer.js'],
        // entries: ['lib/**/*.js']
      }
    },
    uglify: {
      options: {
        // banner: grunt.file.read('LICENCE-MIT'),
        mangle: { toplevel: true },
        squeeze: { dead_code: false },
        codegen: { quote_keys: true }
      },
      dist: {
        files: {
          "dist/bundle.min.js": ["dist/bundle.js"]
        }
      }
    },
    watch: {
      files: ['**/lib/*', 'index.html', 'Gruntfile.js'],
      tasks: ['default']
    },
    connect: {
      server: {
        options: {
          base: '.',
          keepalive: true
        }
      }
    }
  });

  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-simple-mocha');
  grunt.loadNpmTasks('grunt-browserify');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-connect');

  // Default task.
  grunt.registerTask('default', [/*'jshint',*/ 'simplemocha', 'browserify'/*, 'uglify'*/]);

};
<commit_msg>adding testing data generation server<commit_after>'use strict';

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      gruntfile: {
        src: 'Gruntfile.js'
      },
      lib: {
        src: ['lib/**/*.js']
      },
      test: {
        src: ['test/**/*.js']
      },
    },
    simplemocha: {
      options: {
        globals: ['should'],
        uid: 'bdd',
        reporter: 'tap'
      },
      all: {
        src: 'test/**/*-test.js'
      }
    },
    browserify: {
      'dist/bundle.js': {
        requires: ['./lib/viewer.js'],
        // entries: ['lib/**/*.js']
      }
    },
    uglify: {
      options: {
        // banner: grunt.file.read('LICENCE-MIT'),
        mangle: { toplevel: true },
        squeeze: { dead_code: false },
        codegen: { quote_keys: true }
      },
      dist: {
        files: {
          "dist/bundle.min.js": ["dist/bundle.js"]
        }
      }
    },
    watch: {
      files: ['**/lib/*', 'index.html', 'Gruntfile.js'],
      tasks: ['default']
    },
    connect: {
      server: {
        options: {
          base: '.',
          keepalive: true
        }
      }
    }
  });

  grunt.registerTask('server', 'Start a custom server.', function() {
    var done = this.async();
    var port = 9000;
    grunt.log.writeln('Starting a web server on port ' + port);

    var io = require('socket.io').listen(port).on('close', done);

    io.sockets.on('connection', function(socket) {
      var recursive = function () {
          console.log("It has been one second!");
          socket.emit('stream', {"stream": [{"type": "node", "id": "H"}/*, {"type": "link", "source": "H", "target": "A"}*/]});
          setTimeout(recursive, 1000);
      };
      recursive();

      socket.on('ready', function() {
          socket.emit('data', {
          "nodes": [
            {"id": "A"},
            {"id": "B"},
            {"id": "C"},
            {"id": "D"},
            {"id": "E"},
            {"id": "F"},
            {"id": "G"}
          ],
          "links": [
            {"source": "A", "target": "B"},
            {"source": "B", "target": "C"},
            {"source": "A", "target": "D"},
            {"source": "B", "target": "E"},
            {"source": "C", "target": "E"},
            {"source": "D", "target": "E"},
            {"source": "E", "target": "F"},
            {"source": "F", "target": "G"}]
        });
      });
    });
  });

  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-simple-mocha');
  grunt.loadNpmTasks('grunt-browserify');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-connect');

  // Default task.
  grunt.registerTask('default', [/*'jshint',*/ 'simplemocha', 'browserify', 'connect'/*, 'uglify'*/]);

};
<|endoftext|>