<commit_before>// SelectionDetail keeps the selection detail info panel up to date
// use select_journal and select_author to update the display
// with what the user has currently selected.
var SelectionDetail = function(journals, authors) {
	this.author_info_div = d3.select("#author_info");
	this.journal_info_div = d3.select("#journal_info");
	this.journal_picker = d3.select("#journal_picker");

	this.article_html =
		'
' +
			'
Article Title
' +
		'
' +
		'
' +
			'
Authors: Clicking an author should select them
' +
			'
Year:
' +
			'Doi:' +
		'
';

	this.journals = journals;
	this.authors = authors;

	// Hide the author info div as it's not currently being shown
	this.author_info_div.style("display", "none");
	// Also hide the journal info since we're not showing a journal either
	this.journal_info_div.style("display", "none");
	// Append the journals in the list to the picker
	for (var key in journals){
		if (journals.hasOwnProperty(key)){
			var j = journals[key];
			console.log("appending journal " + j.title);
			this.journal_picker.append("option").html(j.title)
				.attr("value", j.short_name);
		}
	}
	// Start by selecting the first journal
	this.select_journal(journals[Object.keys(journals)[0]]);
	var self = this;
	// TODO: We should also emit an event that the d3 visualization can pick up
	this.journal_picker.on("change", function(){
		self.select_journal(self.journals[this.value]);
	});

	// Debugging: pick an author
	this.select_author(authors[400]);
}
// Update the selection detail panel to display information about the
// journal selected by the user
SelectionDetail.prototype.select_journal = function(journal) {
	this.journal_info_div.style("display", "");
	this.author_info_div.style("display", "none");

	this.journal_info_div.select("#journal_title").text(journal.title);
	this.journal_info_div.select("#journal_article_count").text(journal.num_articles);
	this.journal_info_div.select("#journal_years").html(journal.first_year + " – " + journal.latest_year);

	// Select and update the journals's article panels, adding/removing as needed
	var publications = this.journal_info_div.select("#journal_papers_list");
	var article_list = publications.selectAll(".panel")
		.data(journal.articles);
	article_list.exit().remove();
	article_list.enter().append("div")
		.attr("class", "panel panel-default")
		.html(this.article_html);
	article_list.each(function(d) { update_article(d3.select(this), d); });
}
// Update the selection detail panel to display information about the
// author currently selected by the user.
SelectionDetail.prototype.select_author = function(author) {
	this.journal_info_div.style("display", "none");
	this.author_info_div.style("display", "");
	this.author_info_div.select("#author_name").text(author.name);
	this.author_info_div.select("#author_affiliation").text(author.affiliation);

	// Clear any collaborators that were shown previously and replace them
	// with this author's collaborators
	var collaborators = this.author_info_div.select("#author_collaborators_list");
	collaborators.html("");

	// Select and update the author's article panels, adding/removing as needed
	var publications = this.author_info_div.select("#author_papers_list");
	var article_list = publications.selectAll(".panel")
		.data(author.articles);
	article_list.exit().remove();
	article_list.enter().append("div")
		.attr("class", "panel panel-default")
		.html(this.article_html);
	article_list.each(function(d) { update_article(d3.select(this), d); });
}
function update_article(element, article) {
	element.select("#title").html(article.title);
	element.select("#authors").html("authors go here");
	element.select("#year").html(article.year);
	element.select("#doi").html(article.doi)
		.attr("href", article.doi);
}

<commit_msg>Now show the list of authors which can be clicked to select them<commit_after>// SelectionDetail keeps the selection detail info panel up to date
// use select_journal and select_author to update the display
// with what the user has currently selected.
var SelectionDetail = function(journals, authors) {
	this.author_info_div = d3.select("#author_info");
	this.journal_info_div = d3.select("#journal_info");
	this.journal_picker = d3.select("#journal_picker");

	this.article_html =
		'
' +
			'
Article Title
' +
		'
' +
		'
' +
			'Authors:' +
			'

' +
			'Year Published:' +
			'

' +
			'' +
		'
';

	this.journals = journals;
	this.authors = authors;

	// Hide the author info div as it's not currently being shown
	this.author_info_div.style("display", "none");
	// Also hide the journal info since we're not showing a journal either
	this.journal_info_div.style("display", "none");
	// Append the journals in the list to the picker
	for (var key in journals){
		if (journals.hasOwnProperty(key)){
			var j = journals[key];
			console.log("appending journal " + j.title);
			this.journal_picker.append("option").html(j.title)
				.attr("value", j.short_name);
		}
	}
	// Start by selecting the first journal
	//this.select_journal(journals[Object.keys(journals)[0]]);
	var self = this;
	// TODO: We should also emit an event that the d3 visualization can pick up
	this.journal_picker.on("change", function(){
		self.select_journal(self.journals[this.value]);
	});

	// Debugging: pick an author
	this.select_author(authors[400]);
}
// Update the selection detail panel to display information about the
// journal selected by the user
SelectionDetail.prototype.select_journal = function(journal) {
	this.journal_info_div.style("display", "");
	this.author_info_div.style("display", "none");

	this.journal_info_div.select("#journal_title").text(journal.title);
	this.journal_info_div.select("#journal_article_count").text(journal.num_articles);
	this.journal_info_div.select("#journal_years").html(journal.first_year + " – " + journal.latest_year);

	// Select and update the journals's article panels, adding/removing as needed
	var publications = this.journal_info_div.select("#journal_papers_list");
	var article_list = publications.selectAll(".panel")
		.data(journal.articles);
	article_list.exit().remove();
	article_list.enter().append("div")
		.attr("class", "panel panel-default")
		.html(this.article_html);
	var self = this;
	article_list.each(function(d) { self.update_article(d3.select(this), d); });
}
// Update the selection detail panel to display information about the
// author currently selected by the user.
SelectionDetail.prototype.select_author = function(author) {
	this.journal_info_div.style("display", "none");
	this.author_info_div.style("display", "");
	this.author_info_div.select("#author_name").text(author.name);
	this.author_info_div.select("#author_affiliation").text(author.affiliation);

	// Clear any collaborators that were shown previously and replace them
	// with this author's collaborators
	var collaborators = this.author_info_div.select("#author_collaborators_list");
	collaborators.html("");

	// Select and update the author's article panels, adding/removing as needed
	var publications = this.author_info_div.select("#author_papers_list");
	var article_list = publications.selectAll(".panel")
		.data(author.articles);
	article_list.exit().remove();
	article_list.enter().append("div")
		.attr("class", "panel panel-default")
		.html(this.article_html);
	var self = this;
	article_list.each(function(d) { self.update_article(d3.select(this), d); });
}
SelectionDetail.prototype.update_article = function(element, article) {
	element.select("#title").html(article.title);
	var self = this;
	var author_list = element.select("#authors").selectAll("a")
		.data(article.authors);
	author_list.exit().remove();
	author_list.enter().append("a")
		.attr("href", "javascript:void(0)");
	author_list.on("click", function(d) {
			self.select_author(self.authors[d]);
		})
		.text(function(d, i) {
			if (i + 1 < article.authors.length) {
				return self.authors[d].name + ", ";
			} else {
				return self.authors[d].name;
			}
		});
	element.select("#year").html(article.year);
	element.select("#doi").html(article.doi)
		.attr("href", article.doi);
}

<|endoftext|>


<commit_before>import IndexPage from './components/IndexPage';
import DiscussionPage from './components/DiscussionPage';
import PostsUserPage from './components/PostsUserPage';
import DiscussionsUserPage from './components/DiscussionsUserPage';
import SettingsPage from './components/SettingsPage';
import NotificationsPage from './components/NotificationsPage';
import DiscussionPageResolver from './resolver/DiscussionPageResolver';

/**
 * The `routes` initializer defines the forum app's routes.
 *
 * @param {App} app
 */
export default function (app) {
  app.routes = {
    index: { path: '/all', component: IndexPage },

    discussion: { path: '/d/:id', component: DiscussionPage, resolverClass: DiscussionPageResolver },
    'discussion.near': { path: '/d/:id/:near', component: DiscussionPage, resolverClass: DiscussionPageResolver },

    user: { path: '/u/:username', component: PostsUserPage },
    'user.posts': { path: '/u/:username', component: PostsUserPage },
    'user.discussions': { path: '/u/:username/discussions', component: DiscussionsUserPage },

    settings: { path: '/settings', component: SettingsPage },
    notifications: { path: '/notifications', component: NotificationsPage },
  };

  /**
   * Generate a URL to a discussion.
   *
   * @param {Discussion} discussion
   * @param {Integer} [near]
   * @return {String}
   */
  app.route.discussion = (discussion, near) => {
    const slug = discussion.slug();
    return app.route(near && near !== 1 ? 'discussion.near' : 'discussion', {
      id: discussion.id() + (slug.trim() ? '-' + slug : ''),
      near: near && near !== 1 ? near : undefined,
    });
  };

  /**
   * Generate a URL to a post.
   *
   * @param {Post} post
   * @return {String}
   */
  app.route.post = (post) => {
    return app.route.discussion(post.discussion(), post.number());
  };

  /**
   * Generate a URL to a user.
   *
   * @param {User} user
   * @return {String}
   */
  app.route.user = (user) => {
    return app.route('user', {
      username: user.username(),
    });
  };
}
<commit_msg>Fix broken import<commit_after>import IndexPage from './components/IndexPage';
import DiscussionPage from './components/DiscussionPage';
import PostsUserPage from './components/PostsUserPage';
import DiscussionsUserPage from './components/DiscussionsUserPage';
import SettingsPage from './components/SettingsPage';
import NotificationsPage from './components/NotificationsPage';
import DiscussionPageResolver from './resolvers/DiscussionPageResolver';

/**
 * The `routes` initializer defines the forum app's routes.
 *
 * @param {App} app
 */
export default function (app) {
  app.routes = {
    index: { path: '/all', component: IndexPage },

    discussion: { path: '/d/:id', component: DiscussionPage, resolverClass: DiscussionPageResolver },
    'discussion.near': { path: '/d/:id/:near', component: DiscussionPage, resolverClass: DiscussionPageResolver },

    user: { path: '/u/:username', component: PostsUserPage },
    'user.posts': { path: '/u/:username', component: PostsUserPage },
    'user.discussions': { path: '/u/:username/discussions', component: DiscussionsUserPage },

    settings: { path: '/settings', component: SettingsPage },
    notifications: { path: '/notifications', component: NotificationsPage },
  };

  /**
   * Generate a URL to a discussion.
   *
   * @param {Discussion} discussion
   * @param {Integer} [near]
   * @return {String}
   */
  app.route.discussion = (discussion, near) => {
    const slug = discussion.slug();
    return app.route(near && near !== 1 ? 'discussion.near' : 'discussion', {
      id: discussion.id() + (slug.trim() ? '-' + slug : ''),
      near: near && near !== 1 ? near : undefined,
    });
  };

  /**
   * Generate a URL to a post.
   *
   * @param {Post} post
   * @return {String}
   */
  app.route.post = (post) => {
    return app.route.discussion(post.discussion(), post.number());
  };

  /**
   * Generate a URL to a user.
   *
   * @param {User} user
   * @return {String}
   */
  app.route.user = (user) => {
    return app.route('user', {
      username: user.username(),
    });
  };
}
<|endoftext|>


<commit_before>/* vim: ts=4:sw=4:expandtab
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function () {
    'use strict';

    window.Whisper = window.Whisper || {};

    Whisper.InboxView = Backbone.View.extend({
        initialize: function () {
            this.$gutter = $('#gutter');
            this.$contacts = $('#contacts');
            this.$fab = this.$el.find('.fab');
            this.$back = this.$el.find('.back');

            this.newConversationView = new Whisper.NewConversationView();
            this.newConversationView.$el.hide().appendTo(this.$gutter);

            this.conversations = new Whisper.ConversationCollection();
            this.inbox = new Whisper.ConversationListView({
                el         : this.$contacts,
                collection : this.conversations
            });

            this.$el.addClass('loading');
            this.conversations.fetchActive({reset: true}).then(function() {
                this.$el.removeClass('loading');
            }.bind(this));

            extension.on('message', function(message) {
                this.conversations.fetch({id: message.conversationId}).then(function() {
                    this.conversations.get(message.conversationId).fetchMessages();
                }.bind(this));
            }.bind(this));
        },
        events: {
            'click .back': 'hideCompose',
            'click .fab': 'showCompose',
            'keyup input.new-message': 'compose'
        },
        showCompose: function() {
            this.$fab.hide();
            this.$contacts.hide();
            this.newConversationView.$el.show();
            this.newConversationView.reset();
            this.$back.show();
        },
        hideCompose: function() {
            this.newConversationView.$el.hide();
            this.$contacts.show();
            this.$fab.show();
            this.$back.hide();
        },
        compose: function(e) {
            if (e.keyCode === 27) {
                this.hideCompose();
            }
        }
    });

})();
<commit_msg>Reset the typahead before showing it<commit_after>/* vim: ts=4:sw=4:expandtab
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function () {
    'use strict';

    window.Whisper = window.Whisper || {};

    Whisper.InboxView = Backbone.View.extend({
        initialize: function () {
            this.$gutter = $('#gutter');
            this.$contacts = $('#contacts');
            this.$fab = this.$el.find('.fab');
            this.$back = this.$el.find('.back');

            this.newConversationView = new Whisper.NewConversationView();
            this.newConversationView.$el.hide().appendTo(this.$gutter);

            this.conversations = new Whisper.ConversationCollection();
            this.inbox = new Whisper.ConversationListView({
                el         : this.$contacts,
                collection : this.conversations
            });

            this.$el.addClass('loading');
            this.conversations.fetchActive({reset: true}).then(function() {
                this.$el.removeClass('loading');
            }.bind(this));

            extension.on('message', function(message) {
                this.conversations.fetch({id: message.conversationId}).then(function() {
                    this.conversations.get(message.conversationId).fetchMessages();
                }.bind(this));
            }.bind(this));
        },
        events: {
            'click .back': 'hideCompose',
            'click .fab': 'showCompose',
            'keyup input.new-message': 'compose'
        },
        showCompose: function() {
            this.$fab.hide();
            this.$contacts.hide();
            this.newConversationView.reset();
            this.newConversationView.$el.show();
            this.$back.show();
        },
        hideCompose: function() {
            this.newConversationView.$el.hide();
            this.$contacts.show();
            this.$fab.show();
            this.$back.hide();
        },
        compose: function(e) {
            if (e.keyCode === 27) {
                this.hideCompose();
            }
        }
    });

})();
<|endoftext|>


<commit_before>/*
 * Adapted from Swipe.js 2.0
 *
 * Brad Birdsall
 * Copyright 2013, MIT License
 *
*/

(function(ionic) {
'use strict';

ionic.views.Slider = ionic.views.View.inherit({
  initialize: function (options) {
    var slider = this;

    // utilities
    var noop = function() {}; // simple no operation function
    var offloadFn = function(fn) { setTimeout(fn || noop, 0); }; // offload a functions execution

    // check browser capabilities
    var browser = {
      addEventListener: !!window.addEventListener,
      touch: ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch,
      transitions: (function(temp) {
        var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];
        for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true;
        return false;
      })(document.createElement('swipe'))
    };

    var container = options.el;

    // quit if no root element
    if (!container) return;
    var element = container.children[0];
    var slides, slidePos, width, length;
    options = options || {};
    var index = parseInt(options.startSlide, 10) || 0;
    var speed = options.speed || 300;
    options.continuous = options.continuous !== undefined ? options.continuous : true;

    function setup() {

      // cache slides
      slides = element.children;
      length = slides.length;

      // set continuous to false if only one slide
      if (slides.length < 2) options.continuous = false;

      //special case if two slides
      if (browser.transitions && options.continuous && slides.length < 3) {
        element.appendChild(slides[0].cloneNode(true));
        element.appendChild(element.children[1].cloneNode(true));
        slides = element.children;
      }

      // create an array to store current positions of each slide
      slidePos = new Array(slides.length);

      // determine width of each slide
      width = container.offsetWidth || container.getBoundClientRect().width;

      element.style.width = (slides.length * width) + 'px';

      // stack elements
      var pos = slides.length;
      while(pos--) {

        var slide = slides[pos];

        slide.style.width = width + 'px';
        slide.setAttribute('data-index', pos);

        if (browser.transitions) {
          slide.style.left = (pos * -width) + 'px';
          move(pos, index > pos ? -width : (index < pos ? width : 0), 0);
        }

      }

      // reposition elements before and after index
      if (options.continuous && browser.transitions) {
        move(circle(index-1), -width, 0);
        move(circle(index+1), width, 0);
      }

      if (!browser.transitions) element.style.left = (index * -width) + 'px';

      container.style.visibility = 'visible';

      options.slidesChanged && options.slidesChanged();
    }

    function prev() {

      if (options.continuous) slide(index-1);
      else if (index) slide(index-1);

    }

    function next() {

      if (options.continuous) slide(index+1);
      else if (index < slides.length - 1) slide(index+1);

    }

    function circle(index) {

      // a simple positive modulo using slides.length
      return (slides.length + (index % slides.length)) % slides.length;

    }

    function slide(to, slideSpeed) {

      // do nothing if already on requested slide
      if (index == to) return;

      if (browser.transitions) {

        var direction = Math.abs(index-to) / (index-to); // 1: backward, -1: forward

        // get the actual position of the slide
        if (options.continuous) {
          var natural_direction = direction;
          direction = -slidePos[circle(to)] / width;

          // if going forward but to < index, use to = slides.length + to
          // if going backward but to > index, use to = -slides.length + to
          if (direction !== natural_direction) to =  -direction * slides.length + to;

        }

        var diff = Math.abs(index-to) - 1;

        // move all the slides between index and to in the right direction
        while (diff--) move( circle((to > index ? to : index) - diff - 1), width * direction, 0);

        to = circle(to);

        move(index, width * direction, slideSpeed || speed);
        move(to, 0, slideSpeed || speed);

        if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place

      } else {

        to = circle(to);
        animate(index * -width, to * -width, slideSpeed || speed);
        //no fallback for a circular continuous if the browser does not accept transitions
      }

      index = to;
      offloadFn(options.callback && options.callback(index, slides[index]));
    }

    function move(index, dist, speed) {

      translate(index, dist, speed);
      slidePos[index] = dist;

    }

    function translate(index, dist, speed) {

      var slide = slides[index];
      var style = slide && slide.style;

      if (!style) return;

      style.webkitTransitionDuration =
      style.MozTransitionDuration =
      style.msTransitionDuration =
      style.OTransitionDuration =
      style.transitionDuration = speed + 'ms';

      style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)';
      style.msTransform =
      style.MozTransform =
      style.OTransform = 'translateX(' + dist + 'px)';

    }

    function animate(from, to, speed) {

      // if not an animation, just reposition
      if (!speed) {

        element.style.left = to + 'px';
        return;

      }

      var start = +new Date();

      var timer = setInterval(function() {

        var timeElap = +new Date() - start;

        if (timeElap > speed) {

          element.style.left = to + 'px';

          if (delay) begin();

          options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);

          clearInterval(timer);
          return;

        }

        element.style.left = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px';

      }, 4);

    }

    // setup auto slideshow
    var delay = options.auto || 0;
    var interval;

    function begin() {

      interval = setTimeout(next, delay);

    }

    function stop() {

      delay = options.auto || 0;
      clearTimeout(interval);

    }

    // setup initial vars
    var start = {};
    var delta = {};
    var isScrolling;

    // setup event capturing
    var events = {

      handleEvent: function(event) {
        if(event.type == 'mousedown' || event.type == 'mouseup' || event.type == 'mousemove') {
          event.touches = [{
            pageX: event.pageX,
            pageY: event.pageY
          }];
        }

        switch (event.type) {
          case 'mousedown': this.start(event); break;
          case 'touchstart': this.start(event); break;
          case 'touchmove': this.touchmove(event); break;
          case 'mousemove': this.touchmove(event); break;
          case 'touchend': offloadFn(this.end(event)); break;
          case 'mouseup': offloadFn(this.end(event)); break;
          case 'webkitTransitionEnd':
          case 'msTransitionEnd':
          case 'oTransitionEnd':
          case 'otransitionend':
          case 'transitionend': offloadFn(this.transitionEnd(event)); break;
          case 'resize': offloadFn(setup); break;
        }

        if (options.stopPropagation) event.stopPropagation();

      },
      start: function(event) {

        var touches = event.touches[0];

        // measure start values
        start = {

          // get initial touch coords
          x: touches.pageX,
          y: touches.pageY,

          // store time to determine touch duration
          time: +new Date()

        };

        // used for testing first move event
        isScrolling = undefined;

        // reset delta and end measurements
        delta = {};

        // attach touchmove and touchend listeners
        if(browser.touch) {
          element.addEventListener('touchmove', this, false);
          element.addEventListener('touchend', this, false);
        } else {
          element.addEventListener('mousemove', this, false);
          element.addEventListener('mouseup', this, false);
          document.addEventListener('mouseup', this, false);
        }
      },
      touchmove: function(event) {

        // ensure swiping with one touch and not pinching
        // ensure sliding is enabled
        if (event.touches.length > 1 ||
            event.scale && event.scale !== 1 ||
            slider.slideIsDisabled) {
          return;
        }

        if (options.disableScroll) event.preventDefault();

        var touches = event.touches[0];

        // measure change in x and y
        delta = {
          x: touches.pageX - start.x,
          y: touches.pageY - start.y
        };

        // determine if scrolling test has run - one time test
        if ( typeof isScrolling == 'undefined') {
          isScrolling = !!( isScrolling || Math.abs(delta.x) < Math.abs(delta.y) );
        }

        // if user is not trying to scroll vertically
        if (!isScrolling) {

          // prevent native scrolling
          event.preventDefault();

          // stop slideshow
          stop();

          // increase resistance if first or last slide
          if (options.continuous) { // we don't add resistance at the end

            translate(circle(index-1), delta.x + slidePos[circle(index-1)], 0);
            translate(index, delta.x + slidePos[index], 0);
            translate(circle(index+1), delta.x + slidePos[circle(index+1)], 0);

          } else {

            delta.x =
              delta.x /
                ( (!index && delta.x > 0 ||         // if first slide and sliding left
                  index == slides.length - 1 &&     // or if last slide and sliding right
                  delta.x < 0                       // and if sliding at all
                ) ?
                ( Math.abs(delta.x) / width + 1 )      // determine resistance level
                : 1 );                                 // no resistance if false

            // translate 1:1
            translate(index-1, delta.x + slidePos[index-1], 0);
            translate(index, delta.x + slidePos[index], 0);
            translate(index+1, delta.x + slidePos[index+1], 0);
          }

        }

      },
      end: function(event) {

        // measure duration
        var duration = +new Date() - start.time;

        // determine if slide attempt triggers next/prev slide
        var isValidSlide =
              Number(duration) < 250 &&         // if slide duration is less than 250ms
              Math.abs(delta.x) > 20 ||         // and if slide amt is greater than 20px
              Math.abs(delta.x) > width/2;      // or if slide amt is greater than half the width

        // determine if slide attempt is past start and end
        var isPastBounds = (!index && delta.x > 0) ||      // if first slide and slide amt is greater than 0
              (index == slides.length - 1 && delta.x < 0); // or if last slide and slide amt is less than 0

        if (options.continuous) isPastBounds = false;

        // determine direction of swipe (true:right, false:left)
        var direction = delta.x < 0;

        // if not scrolling vertically
        if (!isScrolling) {

          if (isValidSlide && !isPastBounds) {

            if (direction) {

              if (options.continuous) { // we need to get the next in this direction in place

                move(circle(index-1), -width, 0);
                move(circle(index+2), width, 0);

              } else {
                move(index-1, -width, 0);
              }

              move(index, slidePos[index]-width, speed);
              move(circle(index+1), slidePos[circle(index+1)]-width, speed);
              index = circle(index+1);

            } else {
              if (options.continuous) { // we need to get the next in this direction in place

                move(circle(index+1), width, 0);
                move(circle(index-2), -width, 0);

              } else {
                move(index+1, width, 0);
              }

              move(index, slidePos[index]+width, speed);
              move(circle(index-1), slidePos[circle(index-1)]+width, speed);
              index = circle(index-1);

            }

            options.callback && options.callback(index, slides[index]);

          } else {

            if (options.continuous) {

              move(circle(index-1), -width, speed);
              move(index, 0, speed);
              move(circle(index+1), width, speed);

            } else {

              move(index-1, -width, speed);
              move(index, 0, speed);
              move(index+1, width, speed);
            }

          }

        }

        // kill touchmove and touchend event listeners until touchstart called again
        if(browser.touch) {
          element.removeEventListener('touchmove', events, false);
          element.removeEventListener('touchend', events, false);
        } else {
          element.removeEventListener('mousemove', events, false);
          element.removeEventListener('mouseup', events, false);
          document.removeEventListener('mouseup', events, false);
        }

      },
      transitionEnd: function(event) {

        if (parseInt(event.target.getAttribute('data-index'), 10) == index) {

          if (delay) begin();

          options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);

        }

      }

    };

    // Public API
    this.update = function() {
      setTimeout(setup);
    };
    this.setup = function() {
      setup();
    };

    this.enableSlide = function(shouldEnable) {
      if (arguments.length) {
        this.slideIsDisabled = !shouldEnable;
      }
      return !this.slideIsDisabled;
    },
    this.slide = function(to, speed) {
      // cancel slideshow
      stop();

      slide(to, speed);
    };

    this.prev = this.previous = function() {
      // cancel slideshow
      stop();

      prev();
    };

    this.next = function() {
      // cancel slideshow
      stop();

      next();
    };

    this.stop = function() {
      // cancel slideshow
      stop();
    };

    this.start = function() {
      begin();
    };

    this.currentIndex = function() {
      // return current index position
      return index;
    };

    this.slidesCount = function() {
      // return total number of slides
      return length;
    };

    this.kill = function() {
      // cancel slideshow
      stop();

      // reset element
      element.style.width = '';
      element.style.left = '';

      // reset slides
      var pos = slides.length;
      while(pos--) {

        var slide = slides[pos];
        slide.style.width = '';
        slide.style.left = '';

        if (browser.transitions) translate(pos, 0, 0);

      }

      // removed event listeners
      if (browser.addEventListener) {

        // remove current event listeners
        element.removeEventListener('touchstart', events, false);
        element.removeEventListener('webkitTransitionEnd', events, false);
        element.removeEventListener('msTransitionEnd', events, false);
        element.removeEventListener('oTransitionEnd', events, false);
        element.removeEventListener('otransitionend', events, false);
        element.removeEventListener('transitionend', events, false);
        window.removeEventListener('resize', events, false);

      }
      else {

        window.onresize = null;

      }
    };

    this.load = function() {
      // trigger setup
      setup();

      // start auto slideshow if applicable
      if (delay) begin();

      // add event listeners
      if (browser.addEventListener) {

        // set touchstart event on element
        if (browser.touch) {
          element.addEventListener('touchstart', events, false);
        } else {
          element.addEventListener('mousedown', events, false);
        }

        if (browser.transitions) {
          element.addEventListener('webkitTransitionEnd', events, false);
          element.addEventListener('msTransitionEnd', events, false);
          element.addEventListener('oTransitionEnd', events, false);
          element.addEventListener('otransitionend', events, false);
          element.addEventListener('transitionend', events, false);
        }

        // set resize event on window
        window.addEventListener('resize', events, false);

      } else {

        window.onresize = function () { setup(); }; // to play nice with old IE

      }
    };

  }
});

})(ionic);
<commit_msg>fix(sliderView): "getBoundClientRect" typo<commit_after>/*
 * Adapted from Swipe.js 2.0
 *
 * Brad Birdsall
 * Copyright 2013, MIT License
 *
*/

(function(ionic) {
'use strict';

ionic.views.Slider = ionic.views.View.inherit({
  initialize: function (options) {
    var slider = this;

    // utilities
    var noop = function() {}; // simple no operation function
    var offloadFn = function(fn) { setTimeout(fn || noop, 0); }; // offload a functions execution

    // check browser capabilities
    var browser = {
      addEventListener: !!window.addEventListener,
      touch: ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch,
      transitions: (function(temp) {
        var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];
        for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true;
        return false;
      })(document.createElement('swipe'))
    };

    var container = options.el;

    // quit if no root element
    if (!container) return;
    var element = container.children[0];
    var slides, slidePos, width, length;
    options = options || {};
    var index = parseInt(options.startSlide, 10) || 0;
    var speed = options.speed || 300;
    options.continuous = options.continuous !== undefined ? options.continuous : true;

    function setup() {

      // cache slides
      slides = element.children;
      length = slides.length;

      // set continuous to false if only one slide
      if (slides.length < 2) options.continuous = false;

      //special case if two slides
      if (browser.transitions && options.continuous && slides.length < 3) {
        element.appendChild(slides[0].cloneNode(true));
        element.appendChild(element.children[1].cloneNode(true));
        slides = element.children;
      }

      // create an array to store current positions of each slide
      slidePos = new Array(slides.length);

      // determine width of each slide
      width = container.offsetWidth || container.getBoundingClientRect().width;

      element.style.width = (slides.length * width) + 'px';

      // stack elements
      var pos = slides.length;
      while(pos--) {

        var slide = slides[pos];

        slide.style.width = width + 'px';
        slide.setAttribute('data-index', pos);

        if (browser.transitions) {
          slide.style.left = (pos * -width) + 'px';
          move(pos, index > pos ? -width : (index < pos ? width : 0), 0);
        }

      }

      // reposition elements before and after index
      if (options.continuous && browser.transitions) {
        move(circle(index-1), -width, 0);
        move(circle(index+1), width, 0);
      }

      if (!browser.transitions) element.style.left = (index * -width) + 'px';

      container.style.visibility = 'visible';

      options.slidesChanged && options.slidesChanged();
    }

    function prev() {

      if (options.continuous) slide(index-1);
      else if (index) slide(index-1);

    }

    function next() {

      if (options.continuous) slide(index+1);
      else if (index < slides.length - 1) slide(index+1);

    }

    function circle(index) {

      // a simple positive modulo using slides.length
      return (slides.length + (index % slides.length)) % slides.length;

    }

    function slide(to, slideSpeed) {

      // do nothing if already on requested slide
      if (index == to) return;

      if (browser.transitions) {

        var direction = Math.abs(index-to) / (index-to); // 1: backward, -1: forward

        // get the actual position of the slide
        if (options.continuous) {
          var natural_direction = direction;
          direction = -slidePos[circle(to)] / width;

          // if going forward but to < index, use to = slides.length + to
          // if going backward but to > index, use to = -slides.length + to
          if (direction !== natural_direction) to =  -direction * slides.length + to;

        }

        var diff = Math.abs(index-to) - 1;

        // move all the slides between index and to in the right direction
        while (diff--) move( circle((to > index ? to : index) - diff - 1), width * direction, 0);

        to = circle(to);

        move(index, width * direction, slideSpeed || speed);
        move(to, 0, slideSpeed || speed);

        if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place

      } else {

        to = circle(to);
        animate(index * -width, to * -width, slideSpeed || speed);
        //no fallback for a circular continuous if the browser does not accept transitions
      }

      index = to;
      offloadFn(options.callback && options.callback(index, slides[index]));
    }

    function move(index, dist, speed) {

      translate(index, dist, speed);
      slidePos[index] = dist;

    }

    function translate(index, dist, speed) {

      var slide = slides[index];
      var style = slide && slide.style;

      if (!style) return;

      style.webkitTransitionDuration =
      style.MozTransitionDuration =
      style.msTransitionDuration =
      style.OTransitionDuration =
      style.transitionDuration = speed + 'ms';

      style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)';
      style.msTransform =
      style.MozTransform =
      style.OTransform = 'translateX(' + dist + 'px)';

    }

    function animate(from, to, speed) {

      // if not an animation, just reposition
      if (!speed) {

        element.style.left = to + 'px';
        return;

      }

      var start = +new Date();

      var timer = setInterval(function() {

        var timeElap = +new Date() - start;

        if (timeElap > speed) {

          element.style.left = to + 'px';

          if (delay) begin();

          options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);

          clearInterval(timer);
          return;

        }

        element.style.left = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px';

      }, 4);

    }

    // setup auto slideshow
    var delay = options.auto || 0;
    var interval;

    function begin() {

      interval = setTimeout(next, delay);

    }

    function stop() {

      delay = options.auto || 0;
      clearTimeout(interval);

    }

    // setup initial vars
    var start = {};
    var delta = {};
    var isScrolling;

    // setup event capturing
    var events = {

      handleEvent: function(event) {
        if(event.type == 'mousedown' || event.type == 'mouseup' || event.type == 'mousemove') {
          event.touches = [{
            pageX: event.pageX,
            pageY: event.pageY
          }];
        }

        switch (event.type) {
          case 'mousedown': this.start(event); break;
          case 'touchstart': this.start(event); break;
          case 'touchmove': this.touchmove(event); break;
          case 'mousemove': this.touchmove(event); break;
          case 'touchend': offloadFn(this.end(event)); break;
          case 'mouseup': offloadFn(this.end(event)); break;
          case 'webkitTransitionEnd':
          case 'msTransitionEnd':
          case 'oTransitionEnd':
          case 'otransitionend':
          case 'transitionend': offloadFn(this.transitionEnd(event)); break;
          case 'resize': offloadFn(setup); break;
        }

        if (options.stopPropagation) event.stopPropagation();

      },
      start: function(event) {

        var touches = event.touches[0];

        // measure start values
        start = {

          // get initial touch coords
          x: touches.pageX,
          y: touches.pageY,

          // store time to determine touch duration
          time: +new Date()

        };

        // used for testing first move event
        isScrolling = undefined;

        // reset delta and end measurements
        delta = {};

        // attach touchmove and touchend listeners
        if(browser.touch) {
          element.addEventListener('touchmove', this, false);
          element.addEventListener('touchend', this, false);
        } else {
          element.addEventListener('mousemove', this, false);
          element.addEventListener('mouseup', this, false);
          document.addEventListener('mouseup', this, false);
        }
      },
      touchmove: function(event) {

        // ensure swiping with one touch and not pinching
        // ensure sliding is enabled
        if (event.touches.length > 1 ||
            event.scale && event.scale !== 1 ||
            slider.slideIsDisabled) {
          return;
        }

        if (options.disableScroll) event.preventDefault();

        var touches = event.touches[0];

        // measure change in x and y
        delta = {
          x: touches.pageX - start.x,
          y: touches.pageY - start.y
        };

        // determine if scrolling test has run - one time test
        if ( typeof isScrolling == 'undefined') {
          isScrolling = !!( isScrolling || Math.abs(delta.x) < Math.abs(delta.y) );
        }

        // if user is not trying to scroll vertically
        if (!isScrolling) {

          // prevent native scrolling
          event.preventDefault();

          // stop slideshow
          stop();

          // increase resistance if first or last slide
          if (options.continuous) { // we don't add resistance at the end

            translate(circle(index-1), delta.x + slidePos[circle(index-1)], 0);
            translate(index, delta.x + slidePos[index], 0);
            translate(circle(index+1), delta.x + slidePos[circle(index+1)], 0);

          } else {

            delta.x =
              delta.x /
                ( (!index && delta.x > 0 ||         // if first slide and sliding left
                  index == slides.length - 1 &&     // or if last slide and sliding right
                  delta.x < 0                       // and if sliding at all
                ) ?
                ( Math.abs(delta.x) / width + 1 )      // determine resistance level
                : 1 );                                 // no resistance if false

            // translate 1:1
            translate(index-1, delta.x + slidePos[index-1], 0);
            translate(index, delta.x + slidePos[index], 0);
            translate(index+1, delta.x + slidePos[index+1], 0);
          }

        }

      },
      end: function(event) {

        // measure duration
        var duration = +new Date() - start.time;

        // determine if slide attempt triggers next/prev slide
        var isValidSlide =
              Number(duration) < 250 &&         // if slide duration is less than 250ms
              Math.abs(delta.x) > 20 ||         // and if slide amt is greater than 20px
              Math.abs(delta.x) > width/2;      // or if slide amt is greater than half the width

        // determine if slide attempt is past start and end
        var isPastBounds = (!index && delta.x > 0) ||      // if first slide and slide amt is greater than 0
              (index == slides.length - 1 && delta.x < 0); // or if last slide and slide amt is less than 0

        if (options.continuous) isPastBounds = false;

        // determine direction of swipe (true:right, false:left)
        var direction = delta.x < 0;

        // if not scrolling vertically
        if (!isScrolling) {

          if (isValidSlide && !isPastBounds) {

            if (direction) {

              if (options.continuous) { // we need to get the next in this direction in place

                move(circle(index-1), -width, 0);
                move(circle(index+2), width, 0);

              } else {
                move(index-1, -width, 0);
              }

              move(index, slidePos[index]-width, speed);
              move(circle(index+1), slidePos[circle(index+1)]-width, speed);
              index = circle(index+1);

            } else {
              if (options.continuous) { // we need to get the next in this direction in place

                move(circle(index+1), width, 0);
                move(circle(index-2), -width, 0);

              } else {
                move(index+1, width, 0);
              }

              move(index, slidePos[index]+width, speed);
              move(circle(index-1), slidePos[circle(index-1)]+width, speed);
              index = circle(index-1);

            }

            options.callback && options.callback(index, slides[index]);

          } else {

            if (options.continuous) {

              move(circle(index-1), -width, speed);
              move(index, 0, speed);
              move(circle(index+1), width, speed);

            } else {

              move(index-1, -width, speed);
              move(index, 0, speed);
              move(index+1, width, speed);
            }

          }

        }

        // kill touchmove and touchend event listeners until touchstart called again
        if(browser.touch) {
          element.removeEventListener('touchmove', events, false);
          element.removeEventListener('touchend', events, false);
        } else {
          element.removeEventListener('mousemove', events, false);
          element.removeEventListener('mouseup', events, false);
          document.removeEventListener('mouseup', events, false);
        }

      },
      transitionEnd: function(event) {

        if (parseInt(event.target.getAttribute('data-index'), 10) == index) {

          if (delay) begin();

          options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);

        }

      }

    };

    // Public API
    this.update = function() {
      setTimeout(setup);
    };
    this.setup = function() {
      setup();
    };

    this.enableSlide = function(shouldEnable) {
      if (arguments.length) {
        this.slideIsDisabled = !shouldEnable;
      }
      return !this.slideIsDisabled;
    },
    this.slide = function(to, speed) {
      // cancel slideshow
      stop();

      slide(to, speed);
    };

    this.prev = this.previous = function() {
      // cancel slideshow
      stop();

      prev();
    };

    this.next = function() {
      // cancel slideshow
      stop();

      next();
    };

    this.stop = function() {
      // cancel slideshow
      stop();
    };

    this.start = function() {
      begin();
    };

    this.currentIndex = function() {
      // return current index position
      return index;
    };

    this.slidesCount = function() {
      // return total number of slides
      return length;
    };

    this.kill = function() {
      // cancel slideshow
      stop();

      // reset element
      element.style.width = '';
      element.style.left = '';

      // reset slides
      var pos = slides.length;
      while(pos--) {

        var slide = slides[pos];
        slide.style.width = '';
        slide.style.left = '';

        if (browser.transitions) translate(pos, 0, 0);

      }

      // removed event listeners
      if (browser.addEventListener) {

        // remove current event listeners
        element.removeEventListener('touchstart', events, false);
        element.removeEventListener('webkitTransitionEnd', events, false);
        element.removeEventListener('msTransitionEnd', events, false);
        element.removeEventListener('oTransitionEnd', events, false);
        element.removeEventListener('otransitionend', events, false);
        element.removeEventListener('transitionend', events, false);
        window.removeEventListener('resize', events, false);

      }
      else {

        window.onresize = null;

      }
    };

    this.load = function() {
      // trigger setup
      setup();

      // start auto slideshow if applicable
      if (delay) begin();

      // add event listeners
      if (browser.addEventListener) {

        // set touchstart event on element
        if (browser.touch) {
          element.addEventListener('touchstart', events, false);
        } else {
          element.addEventListener('mousedown', events, false);
        }

        if (browser.transitions) {
          element.addEventListener('webkitTransitionEnd', events, false);
          element.addEventListener('msTransitionEnd', events, false);
          element.addEventListener('oTransitionEnd', events, false);
          element.addEventListener('otransitionend', events, false);
          element.addEventListener('transitionend', events, false);
        }

        // set resize event on window
        window.addEventListener('resize', events, false);

      } else {

        window.onresize = function () { setup(); }; // to play nice with old IE

      }
    };

  }
});

})(ionic);
<|endoftext|>


<commit_before>var mongoose = require('mongoose'),
    _ = require('lodash'),
    Schema = mongoose.Schema,
    ObjectId = Schema.Types.ObjectId,
    models = {};

module.exports = function (schema, options) {
    options = options || {};
    var primaryKeyName;

    var getHistoricalModel = function (model) {
        var connection = options.connection || model.constructor.collection.conn,
            name = options.name || model.constructor.modelName + 's_historical';

        primaryKeyName = options.primaryKeyName || '_id';

        if (!model.constructor.schema.paths[primaryKeyName]) {
            throw new Error('Historical error: Missing primary key `' + primaryKeyName + '` in schema `' + name + '`.');
        }

        var primaryKeyType = (options.primaryKeyType || /* deprecated */ options.idType) || (model.constructor.schema.paths[primaryKeyName].options.type || ObjectId);

        models[model.constructor.modelName] = models[model.constructor.modelName] === undefined ?
            connection.model(name, new Schema({
                document: { type: primaryKeyType, index: true },
                timestamp: {type: Date, default: Date.now, index: true},
                diff: Schema.Types.Mixed
            })) : models[model.constructor.modelName];

        return models[model.constructor.modelName];
    };

    var read = function (o, p) {
        for (var i = 0, a = p.split('.'), l = a.length; i < l; i++) {
            o = o[a[i]];
        }
        return o;
    };

    var write = function (o, p, v) {
        for (var i = 0, a = p.split('.'); i < a.length - 1; i++) {
            var n = a[i];
            if (n in o) {
                o = o[n];
            } else {
                o[n] = {};
                o = o[n];
            }
        }
        o[a[a.length - 1]] = v;
    };

    schema.pre('save', function (next) {
        var me = this,
            HistoricalModel = getHistoricalModel(me),
            modified = _.uniq(me.modifiedPaths()),
            diff = {};

        modified.forEach(function (index) {
            var value = read(me, index);
            if (_.isObject(value) && !_.isArray(value)) {
                return;
            }
            if (value === undefined) {
                write(diff, index, null);
                return;
            }
            write(diff, index, value);
        });

        var historical = new HistoricalModel({
            document: me[primaryKeyName],
            diff: diff
        });
        historical.save(next);
    });

    schema.pre('remove', function (next) {
        var me = this,
            HistoricalModel = getHistoricalModel(me);

        var historical = new HistoricalModel({
            document: me[primaryKeyName],
            diff: null
        });
        historical.save(next);
    });

    schema.methods.historicalSnapshot = function (callback) {
        var me = this,
            HistoricalModel = getHistoricalModel(me);

        callback = _.isFunction(callback) ? callback : function () {
        };

        if (me.modifiedPaths().length) {
            return callback(new Error('Historical error: Attempted to snapshot an unsaved/modified document.'));
        }
        var historical = new HistoricalModel({
            document: me[primaryKeyName],
            diff: me.toObject()
        });
        historical.save(function (e) {
            if (e) {
                return callback(e);
            }
            return callback(null, me);
        });
    };

    schema.methods.historicalClear = function (callback) {
        var me = this,
            HistoricalModel = getHistoricalModel(me);

        callback = _.isFunction(callback) ? callback : function () {
        };

        HistoricalModel.find({document: me[primaryKeyName]}, function (e, objs) {
            if (e) {
                return callback(e);
            }
            me.historicalSnapshot(function (e) {
                if (e) {
                    return callback(e);
                }
                objs.forEach(function (obj) {
                    obj.remove();
                });
                return callback(null, me);
            });
        });
    };

    schema.methods.historicalRestore = function (date, callback) {
        var me = this,
            HistoricalModel = getHistoricalModel(me),
            surrogate = {};

        callback = _.isFunction(callback) ? callback : function () {
        };

        if (!_.isDate(date) || date.getTime() > new Date().getTime()) {
            return callback(new Error('Historical error: Invalid date.'));
        }

        HistoricalModel.find({document: me[primaryKeyName], timestamp: {$lte: date}}, null, {sort: {timestamp: 1}}, function (e, objs) {
            if (e) {
                return callback(e);
            }
            if (!objs) {
                return callback(null, null);
            }

            objs.forEach(function (obj) {
                if (!obj.diff) {
                    surrogate = null;
                }
                else {
                    surrogate = _.merge(surrogate, obj.diff);
                }
            });

            if (!surrogate) {
                return callback(null, null);
            }

            me.set(_.merge(me.toObject(), surrogate));
            return callback(null, me);
        });
    };

    schema.methods.historicalTrim = function (date, callback) {
        var me = this,
            HistoricalModel = getHistoricalModel(me);

        callback = _.isFunction(callback) ? callback : function () {
        };

        if (!_.isDate(date) || date.getTime() > new Date().getTime()) {
            return callback(new Error('Historical error: Invalid date.'));
        }

        me.historicalRestore(date, function (e, obj) {
            if (e) {
                return callback(e);
            }
            if (!obj) {
                return callback(null, me);
            }
            HistoricalModel.remove({document: me[primaryKeyName], timestamp: {$lte: date}}, function (e) {
                if (e) {
                    return callback(e);
                }
                var trimmed = new HistoricalModel({
                    document: me[primaryKeyName],
                    diff: obj.toObject(),
                    timestamp: date
                });
                trimmed.save(function (e) {
                    if (e) {
                        return callback(e);
                    }
                    return callback(null, me);
                });
            });
        });
    };

    schema.methods.historicalDetails = function (date, callback) {
        var me = this,
            HistoricalModel = getHistoricalModel(me);

        callback = _.isFunction(callback) ? callback : function () {
        };

        if (!_.isDate(date) || date.getTime() > new Date().getTime()) {
            return callback(new Error('Historical error: Invalid date.'));
        }

        HistoricalModel.find({document: me[primaryKeyName], timestamp: {$lte: date}}, null, {sort: {timestamp: 1}}, function (e, objs) {
            if (e) {
                return callback(e);
            }
            return callback(null, objs);
        });
    };

    schema.methods.historical = function () {
        var me = this,
            action = null,
            date = new Date(),
            callback = function () {
            },
            args = Array.prototype.slice.call(arguments, 0, 3);

        if (_.isString(args[0])) {
            action = args[0];
        }

        if (_.isDate(args[1])) {
            date = args[1];
        }

        if (_.isFunction(args[args.length - 1])) {
            callback = args[args.length - 1];
        }

        switch (action) {
            case 'snapshot':
                me.historicalSnapshot(callback);
                break;
            case 'clear':
                me.historicalClear(callback);
                break;
            case 'restore':
                me.historicalRestore(date, callback);
                break;
            case 'trim':
                me.historicalTrim(date, callback);
                break;
            case 'history':
            case 'details':
            default:
                me.historicalDetails(date, callback);
        }
    };
};
<commit_msg>Don't record primary key and version fields on a snapshot<commit_after>var mongoose = require('mongoose'),
    _ = require('lodash'),
    Schema = mongoose.Schema,
    ObjectId = Schema.Types.ObjectId,
    models = {};

module.exports = function (schema, options) {
    options = options || {};
    var primaryKeyName;

    var getHistoricalModel = function (model) {
        var connection = options.connection || model.constructor.collection.conn,
            name = options.name || model.constructor.modelName + 's_historical';

        primaryKeyName = options.primaryKeyName || '_id';

        if (!model.constructor.schema.paths[primaryKeyName]) {
            throw new Error('Historical error: Missing primary key `' + primaryKeyName + '` in schema `' + name + '`.');
        }

        var primaryKeyType = (options.primaryKeyType || /* deprecated */ options.idType) || (model.constructor.schema.paths[primaryKeyName].options.type || ObjectId);

        models[model.constructor.modelName] = models[model.constructor.modelName] === undefined ?
            connection.model(name, new Schema({
                document: { type: primaryKeyType, index: true },
                timestamp: {type: Date, default: Date.now, index: true},
                diff: Schema.Types.Mixed
            })) : models[model.constructor.modelName];

        return models[model.constructor.modelName];
    };

    var read = function (o, p) {
        for (var i = 0, a = p.split('.'), l = a.length; i < l; i++) {
            o = o[a[i]];
        }
        return o;
    };

    var write = function (o, p, v) {
        for (var i = 0, a = p.split('.'); i < a.length - 1; i++) {
            var n = a[i];
            if (n in o) {
                o = o[n];
            } else {
                o[n] = {};
                o = o[n];
            }
        }
        o[a[a.length - 1]] = v;
    };

    schema.pre('save', function (next) {
        var me = this,
            HistoricalModel = getHistoricalModel(me),
            modified = _.uniq(me.modifiedPaths()),
            diff = {};

        modified.forEach(function (index) {
            var value = read(me, index);
            if (_.isObject(value) && !_.isArray(value)) {
                return;
            }
            if (value === undefined) {
                write(diff, index, null);
                return;
            }
            write(diff, index, value);
        });

        var historical = new HistoricalModel({
            document: me[primaryKeyName],
            diff: diff
        });
        historical.save(next);
    });

    schema.pre('remove', function (next) {
        var me = this,
            HistoricalModel = getHistoricalModel(me);

        var historical = new HistoricalModel({
            document: me[primaryKeyName],
            diff: null
        });
        historical.save(next);
    });

    schema.methods.historicalSnapshot = function (callback) {
        var me = this,
            HistoricalModel = getHistoricalModel(me);

        callback = _.isFunction(callback) ? callback : function () {
        };

        if (me.modifiedPaths().length) {
            return callback(new Error('Historical error: Attempted to snapshot an unsaved/modified document.'));
        }

        var snapshot = me.toObject();
        delete snapshot[primaryKeyName];
        delete snapshot.__v;

        var historical = new HistoricalModel({
            document: me[primaryKeyName],
            diff: snapshot
        });
        historical.save(function (e) {
            if (e) {
                return callback(e);
            }
            return callback(null, me);
        });
    };

    schema.methods.historicalClear = function (callback) {
        var me = this,
            HistoricalModel = getHistoricalModel(me);

        callback = _.isFunction(callback) ? callback : function () {
        };

        HistoricalModel.find({document: me[primaryKeyName]}, function (e, objs) {
            if (e) {
                return callback(e);
            }
            me.historicalSnapshot(function (e) {
                if (e) {
                    return callback(e);
                }
                objs.forEach(function (obj) {
                    obj.remove();
                });
                return callback(null, me);
            });
        });
    };

    schema.methods.historicalRestore = function (date, callback) {
        var me = this,
            HistoricalModel = getHistoricalModel(me),
            surrogate = {};

        callback = _.isFunction(callback) ? callback : function () {
        };

        if (!_.isDate(date) || date.getTime() > new Date().getTime()) {
            return callback(new Error('Historical error: Invalid date.'));
        }

        HistoricalModel.find({document: me[primaryKeyName], timestamp: {$lte: date}}, null, {sort: {timestamp: 1}}, function (e, objs) {
            if (e) {
                return callback(e);
            }
            if (!objs) {
                return callback(null, null);
            }

            objs.forEach(function (obj) {
                if (!obj.diff) {
                    surrogate = null;
                }
                else {
                    surrogate = _.merge(surrogate, obj.diff);
                }
            });

            if (!surrogate) {
                return callback(null, null);
            }

            me.set(_.merge(me.toObject(), surrogate));
            return callback(null, me);
        });
    };

    schema.methods.historicalTrim = function (date, callback) {
        var me = this,
            HistoricalModel = getHistoricalModel(me);

        callback = _.isFunction(callback) ? callback : function () {
        };

        if (!_.isDate(date) || date.getTime() > new Date().getTime()) {
            return callback(new Error('Historical error: Invalid date.'));
        }

        me.historicalRestore(date, function (e, obj) {
            if (e) {
                return callback(e);
            }
            if (!obj) {
                return callback(null, me);
            }
            HistoricalModel.remove({document: me[primaryKeyName], timestamp: {$lte: date}}, function (e) {
                if (e) {
                    return callback(e);
                }
                var trimmed = new HistoricalModel({
                    document: me[primaryKeyName],
                    diff: obj.toObject(),
                    timestamp: date
                });
                trimmed.save(function (e) {
                    if (e) {
                        return callback(e);
                    }
                    return callback(null, me);
                });
            });
        });
    };

    schema.methods.historicalDetails = function (date, callback) {
        var me = this,
            HistoricalModel = getHistoricalModel(me);

        callback = _.isFunction(callback) ? callback : function () {
        };

        if (!_.isDate(date) || date.getTime() > new Date().getTime()) {
            return callback(new Error('Historical error: Invalid date.'));
        }

        HistoricalModel.find({document: me[primaryKeyName], timestamp: {$lte: date}}, null, {sort: {timestamp: 1}}, function (e, objs) {
            if (e) {
                return callback(e);
            }
            return callback(null, objs);
        });
    };

    schema.methods.historical = function () {
        var me = this,
            action = null,
            date = new Date(),
            callback = function () {
            },
            args = Array.prototype.slice.call(arguments, 0, 3);

        if (_.isString(args[0])) {
            action = args[0];
        }

        if (_.isDate(args[1])) {
            date = args[1];
        }

        if (_.isFunction(args[args.length - 1])) {
            callback = args[args.length - 1];
        }

        switch (action) {
            case 'snapshot':
                me.historicalSnapshot(callback);
                break;
            case 'clear':
                me.historicalClear(callback);
                break;
            case 'restore':
                me.historicalRestore(date, callback);
                break;
            case 'trim':
                me.historicalTrim(date, callback);
                break;
            case 'history':
            case 'details':
            default:
                me.historicalDetails(date, callback);
        }
    };
};
<|endoftext|>


<commit_before>(function(){function g(a,b){if(null==a||"object"!=typeof a||a.constructor!=Object&&a.constructor!=Array)return a;if(a.constructor==Date||a.constructor==RegExp||a.constructor==Function||a.constructor==String||a.constructor==Number||a.constructor==Boolean)return new a.constructor(a);b=b||new a.constructor;for(var c in a)b[c]="undefined"==typeof b[c]?g(a[c],null):b[c];return b}var e=this;e._hookers={};var d={trigger:function(a,b){for(var c=[b],d=e._hookers[a],f=0;f<d.length;f++)c[f]=g(b,{}),b=d[f].callback.apply(d[f]._this,
[b,g(c,[])])},register:function(a,b,c){e._hookers[a]=e._hookers[a]||[];e._hookers[a].push({callback:b,_this:c})}};"undefined"!==typeof module&&"undefined"!==typeof module.exports?module.exports=d:"function"===typeof define&&define.amd?define([],function(){return d}):e.hooker=d})();<commit_msg>Update: Build file<commit_after>(function(){function g(a,b){if(null==a||"object"!=typeof a||a.constructor!=Object&&a.constructor!=Array)return a;if(a.constructor==Date||a.constructor==RegExp||a.constructor==Function||a.constructor==String||a.constructor==Number||a.constructor==Boolean)return new a.constructor(a);b=b||new a.constructor;for(var c in a)b[c]="undefined"==typeof b[c]?g(a[c],null):b[c];return b}var d=this;d._hookers={};var h={trigger:function(a,b){var c=[b],f=d._hookers[a];if("undefined"!==typeof f)for(var e=0;e<f.length;e++)c[e]=
g(b,{}),b=f[e].callback.apply(f[e]._this,[b,g(c,[])])},register:function(a,b,c){d._hookers[a]=d._hookers[a]||[];d._hookers[a].push({callback:b,_this:c})}};"undefined"!==typeof module&&"undefined"!==typeof module.exports?module.exports=h:"function"===typeof define&&define.amd?define([],function(){return h}):d.hooker=h})();<|endoftext|>


<commit_before>/*! genpac __VERSION__ https://github.com/JinnLynn/genpac */
var proxy="__PROXY__",rules=__RULES__,lastRule="",regExpMatch=function(b,c){try{return(new RegExp(c)).test(b)}catch(a){return!1}},testURL=function(b,c){for(var a=0;a<c.length;a++)for(var d=0;d<c[a].length;d++)if(lastRule=c[a][d],0==a%2&&regExpMatch(b,lastRule)||0!=a%2&&shExpMatch(b,lastRule))return 1>=a?"DIRECT":proxy;lastRule=""};function FindProxyForURL(b,c){for(var a=0;a<rules.length;a++){var d=testURL(b,rules[a]);if(void 0!==d)return d}return"DIRECT"};<commit_msg>add build info<commit_after>/*! genpac __VERSION__ https://github.com/JinnLynn/genpac */
var proxy="__PROXY__",rules=__RULES__,lastRule="",regExpMatch=function(b,c){try{return(new RegExp(c)).test(b)}catch(a){return!1}},testURL=function(b,c){for(var a=0;a<c.length;a++)for(var d=0;d<c[a].length;d++)if(lastRule=c[a][d],0==a%2&&regExpMatch(b,lastRule)||0!=a%2&&shExpMatch(b,lastRule))return 1>=a?"DIRECT":proxy;lastRule=""};function FindProxyForURL(b,c){for(var a=0;a<rules.length;a++){var d=testURL(b,rules[a]);if(void 0!==d)return d}return"DIRECT"};
//! Generated: __GENERATED__
//! GFWList: __MODIFIED__ From __GFWLIST_FROM__<|endoftext|>


<commit_before>// Listens for hss-reporter clients

// TODO: exceptions
// TODO: rdns fail handling

var key = 'banana'

var express = require('express')
var app = express()
var http = require('http')
var server = http.createServer(app)
var io = require('socket.io').listen(server)
var dns = require('dns')
var os = require('os')

server.listen( process.env.PORT||80 )

// state to be synchronised with the client
var state = {}
var title = ''

// dict of IP -> [host,domain]
var fqdns = {}

app.use(express.bodyParser())

app.post('/init', function(req, res) {
	if (req.body.key != key)
		return res.json(403,{error:'Wrong API key'})
	else
		delete req.body.key

	var ip = req.connection.remoteAddress

	getFqdn(ip,function(err,host,domain,fqdn) {
		if (err) return res.json(404,{error:"Could not find DNS hostname"})

		fqdns[ip] = [host,domain]

		state[host] = {
			Uptime : {
				units : ' days'
			},
			Load : {
				units : '%',
				max : 100,
				gradient : 'negative'
			},
			Storage : {
				units : 'GB',
				max : req.body.Storage,
				gradient : 'negative'
			},
			Memory : {
				units : 'MB',
				max : req.body.Memory,
				gradient : 'negative'
			}
		}

		if (req.body.Temperature)
			state[host].Temperature = {
				units : '°C',
				max : 80,
				gradient : 'negative',
				min : 15,
				alarm :true
			}

		if (req.body.Traffic)
			state[host].Traffic = {
				units : 'Mbps',
				max : req.body.Traffic,
				gradient : 'negative'
			}

		io.sockets.emit('refresh',state)

		res.json({
			success  : "Initialised server to pool",
			hostname : host,
			domain   : domain
		})
	})
})

app.post('/update', function(req, res){
	if (req.body.key != key)
		res.json({error:'Wrong API key'})
	else
		delete req.body.key

	var ip = req.connection.remoteAddress
	var host = fqdns[ip][0]
	var update = {}
	update[host] = {}

	for (var attr in req.body)
		state[host][attr].value = update[host][attr] = req.body[attr]

	io.sockets.emit('update',update)
	res.json({success:'Updated'})
})

app.use(express.static(__dirname + '/www'))

io.set('log level',1)
io.enable('browser client minification')
io.enable('browser client etag')
io.enable('browser client gzip')
io.set('transports', ['websocket','flashsocket','htmlfile','xhr-polling','jsonp-polling'])

// longhand defining parameters of attributes
var initial = {}

// new client
io.sockets.on('connection',function (socket) {
	socket.emit('refresh',state)
	socket.emit('title',title)
})

// given an ip, callback with host and domain and fqdn
function getFqdn(ip,cb) {
	dns.reverse(ip,function(err,domains){
		if (err) return cb(err)

		if (ip == '127.0.0.1') return cb(null,os.hostname(),null,null)

		if (domains.length == 0) return cb(true)

		var parts = domains[0].split('.')
		var host = parts.shift() || null
		var domain = parts.join('.') || null

		cb(null,host,domain,domains[0])
	})
}
<commit_msg>dormant server automatic removal<commit_after>// Listens for hss-reporter clients

// TODO: exceptions
// TODO: rdns fail handling

var key = 'banana'

// shared domain name for servers, or false if servers
// no not have a common domain name
var common_domain = false

var express = require('express')
var app = express()
var http = require('http')
var server = http.createServer(app)
var io = require('socket.io').listen(server)
var dns = require('dns')
var os = require('os')

server.listen( process.env.PORT||80 )

// state to be synchronised with the client
var state = {}
var title = ''

// dict of IP -> [host,domain]
var fqdns = {}

// object, host -> date last updated
// to find dormant servers
var updated = {}

// maximum time in seconds a server can go quiet for without
// removing it
var max_dormant = 30

app.use(express.bodyParser())

app.post('/init', function(req, res) {
	if (req.body.key != key)
		return res.json(403,{error:'Wrong API key'})
	else
		delete req.body.key

	var ip = req.connection.remoteAddress

	getFqdn(ip,function(err,host,domain,fqdn) {
		if (err) return res.json(404,{error:"Could not find DNS hostname"})

		fqdns[ip] = [host,domain,fqdn]

		state[host] = {
			Uptime : {
				units : ' days'
			},
			Load : {
				units : '%',
				max : 100,
				gradient : 'negative'
			},
			Storage : {
				units : 'GB',
				max : req.body.Storage,
				gradient : 'negative'
			},
			Memory : {
				units : 'MB',
				max : req.body.Memory,
				gradient : 'negative'
			}
		}

		if (req.body.Temperature)
			state[host].Temperature = {
				units : '°C',
				max : 80,
				gradient : 'negative',
				min : 15,
				alarm :true
			}

		if (req.body.Traffic)
			state[host].Traffic = {
				units : 'Mbps',
				max : req.body.Traffic,
				gradient : 'negative'
			}

		io.sockets.emit('refresh',state)

		res.json({
			success  : "Initialised server to pool",
			hostname : host,
			domain   : domain
		})
	})
})

app.post('/update', function(req, res){
	if (req.body.key != key)
		res.json({error:'Wrong API key'})
	else
		delete req.body.key

	var ip = req.connection.remoteAddress
	var host = fqdns[ip][0]
	var update = {}
	update[host] = {}

	updated[host] = new Date()

	for (var attr in req.body)
		state[host][attr].value = update[host][attr] = req.body[attr]

	io.sockets.emit('update',update)
	res.json({success:'Updated'})
})

app.use(express.static(__dirname + '/www'))

io.set('log level',1)
io.enable('browser client minification')
io.enable('browser client etag')
io.enable('browser client gzip')
io.set('transports', ['websocket','flashsocket','htmlfile','xhr-polling','jsonp-polling'])

// longhand defining parameters of attributes
var initial = {}

// new client
io.sockets.on('connection',function (socket) {
	socket.emit('refresh',state)
	socket.emit('title',title)
})

// given an ip, callback with host and domain and fqdn
function getFqdn(ip,cb) {
	dns.reverse(ip,function(err,domains){
		if (err) return cb(err)

		if (ip == '127.0.0.1') return cb(null,os.hostname(),null,null)

		if (domains.length == 0) return cb(true)

		var parts = domains[0].split('.')
		var host = parts.shift() || null
		var domain = parts.join('.') || null

		cb(null,host,domain,domains[0])
	})
}

// look for dormant servers and remove them
setInterval(function(){
	for (var host in updated) {
		var min = (new Date()).getTime() - max_dormant*1000

		if ( updated[host].getTime() < min) {
			// remove host and tell clients
			delete state[host]
			io.sockets.emit('refresh',state)
		}
	}
},max_dormant*500)
<|endoftext|>


<commit_before>function getOffset( el ) {
    var _x = 0;
    var _y = 0;
    while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
        _x += el.offsetLeft - el.scrollLeft;
        _y += el.offsetTop - el.scrollTop;
        el = el.offsetParent;
    }
    return { top: _y, left: _x };
}

var offset = getOffset(document.getElementById('chart'));

var w = window.innerWidth - offset.left,
    h = window.innerHeight - offset.top,
    fill = d3.scale.category20();

var vis = d3.select("#chart").append("svg")
    .attr("width", w)
    .attr("height", h);

d3.json("nodes.json", function(json) {
  var force = d3.layout.force()
      .charge(-350)
      .gravity(0.05)
      .friction(0.75)
      .theta(0.1)
      .linkDistance(function (d) { return d.distance; })
      .linkStrength(function (d) { return d.strength; })
      .nodes(json.nodes)
      .links(json.links)
      .size([w, h])
      .start();

  var linkedByIndex = {};

  json.links.forEach(function(d) {
      linkedByIndex[d.source.index + "," + d.target.index] = 1;
  });

  var link = vis.selectAll("line.link")
      .data(json.links)
    .enter().append("line")
      .attr("class", "link")
      .style("stroke-width", function(d) { return Math.min(1, d.strength * 2); })
      .attr("x1", function(d) { return d.source.x; })
      .attr("y1", function(d) { return d.source.y; })
      .attr("x2", function(d) { return d.target.x; })
      .attr("y2", function(d) { return d.target.y; });

  function highlight_nodes(f) {
    return function (e, i) {
      d3.select(this).classed("highlight", f);
              var links = vis.selectAll("line").filter(function(d) {
                        return d.source.index == i || d.target.index == i;
                    }).each(function(dLink, iLink) {
                          d3.select(this).classed("highlight", f);
                      });
    }
  }

    function isConnected(a, b) {
        return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
    }

   function fade(opacity) {
            return function(d) {
                  node.style("stroke-opacity", function(o) {
                        thisOpacity = isConnected(d, o) ? 1 : opacity;
                        this.setAttribute('fill-opacity', thisOpacity);
                        return thisOpacity;
                    });

                  link.style("stroke-opacity", function(o) {
                        return o.source === d || o.target === d ? 1 : opacity;
                    });
              };
        }

  var node = vis.selectAll("svg.node")
      .data(json.nodes)
    .enter().append("g")
      .attr("class", "node")
      .on("mouseover", fade(.1))//highlight_nodes(true))
      .on("mouseout", fade(1))//highlight_nodes(false))
      .call(force.drag);

  node.append("ellipse")
      .attr("rx", function(d) { if (d.group == 3) return 4; else return Math.max(10, d.name.length * 5); })
      .attr("ry", function(d) { if (d.group == 3) return 4; else return 10; })
      .style("fill", function(d) { if (d.group == 3) return fill(d.group); else return ""; })
      .style("stroke", function(d) { return fill(d.group); });

  node.append("text")
      .attr("text-anchor", "middle")
      .attr("y", "4px")
      .text(function(d) { if (d.group == 3) return ""; else return d.name; });

  node.append("title")
      .text(function(d) { return d.macs; });

  force.on("tick", function() {
    link.attr("x1", function(d) { return d.source.x; })
        .attr("y1", function(d) { return d.source.y; })
        .attr("x2", function(d) { return d.target.x; })
        .attr("y2", function(d) { return d.target.y; });

    node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
  });
});
<commit_msg>tweak force constants<commit_after>function getOffset( el ) {
    var _x = 0;
    var _y = 0;
    while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
        _x += el.offsetLeft - el.scrollLeft;
        _y += el.offsetTop - el.scrollTop;
        el = el.offsetParent;
    }
    return { top: _y, left: _x };
}

var offset = getOffset(document.getElementById('chart'));

var w = window.innerWidth - offset.left,
    h = window.innerHeight - offset.top,
    fill = d3.scale.category20();

var vis = d3.select("#chart").append("svg")
    .attr("width", w)
    .attr("height", h);

d3.json("nodes.json", function(json) {
  var force = d3.layout.force()
      .charge(-100)
      .gravity(0.02)
      .friction(0.75)
      .theta(0.1)
      .linkDistance(function (d) { return d.distance; })
      .linkStrength(function (d) { return d.strength; })
      .nodes(json.nodes)
      .links(json.links)
      .size([w, h])
      .start();

  var linkedByIndex = {};

  json.links.forEach(function(d) {
      linkedByIndex[d.source.index + "," + d.target.index] = 1;
  });

  var link = vis.selectAll("line.link")
      .data(json.links)
    .enter().append("line")
      .attr("class", "link")
      .style("stroke-width", function(d) { return Math.min(1, d.strength * 2); })
      .attr("x1", function(d) { return d.source.x; })
      .attr("y1", function(d) { return d.source.y; })
      .attr("x2", function(d) { return d.target.x; })
      .attr("y2", function(d) { return d.target.y; });

  function highlight_nodes(f) {
    return function (e, i) {
      d3.select(this).classed("highlight", f);
              var links = vis.selectAll("line").filter(function(d) {
                        return d.source.index == i || d.target.index == i;
                    }).each(function(dLink, iLink) {
                          d3.select(this).classed("highlight", f);
                      });
    }
  }

    function isConnected(a, b) {
        return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
    }

   function fade(opacity) {
            return function(d) {
                  node.style("stroke-opacity", function(o) {
                        thisOpacity = isConnected(d, o) ? 1 : opacity;
                        this.setAttribute('fill-opacity', thisOpacity);
                        return thisOpacity;
                    });

                  link.style("stroke-opacity", function(o) {
                        return o.source === d || o.target === d ? 1 : opacity;
                    });
              };
        }

  var node = vis.selectAll("svg.node")
      .data(json.nodes)
    .enter().append("g")
      .attr("class", "node")
      .on("mouseover", fade(.1))//highlight_nodes(true))
      .on("mouseout", fade(1))//highlight_nodes(false))
      .call(force.drag);

  node.append("ellipse")
      .attr("rx", function(d) { if (d.group == 3) return 4; else return Math.max(10, d.name.length * 5); })
      .attr("ry", function(d) { if (d.group == 3) return 4; else return 10; })
      .style("fill", function(d) { if (d.group == 3) return fill(d.group); else return ""; })
      .style("stroke", function(d) { return fill(d.group); });

  node.append("text")
      .attr("text-anchor", "middle")
      .attr("y", "4px")
      .text(function(d) { if (d.group == 3) return ""; else return d.name; });

  node.append("title")
      .text(function(d) { return d.macs; });

  force.on("tick", function() {
    link.attr("x1", function(d) { return d.source.x; })
        .attr("y1", function(d) { return d.source.y; })
        .attr("x2", function(d) { return d.target.x; })
        .attr("y2", function(d) { return d.target.y; });

    node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
  });
});
<|endoftext|>