<commit_before>(function() {
/**
 * Markup and styles.
 */
const template = document.createElement('template');
template.innerHTML = `

      <slot id="slidesSlot">
No content available
</slot>

`;

/**
 * XSlider definition.
 */
class XSlider extends HTMLElement {
  static get observedAttributes() {
    return ['selected', 'loop', 'navigation'];
  }

  /**
   * Runs anytime a new instance is created (in HTML or JS).
   * The construtor is a good place to create shadow DOM, though you should
   * avoid touching any attributes or light DOM children as they may not
   * be available yet.
   * @constructor
   */
  constructor() {
    super();

    this.attachShadow({mode: 'open'});
    this.shadowRoot.appendChild(template.content.cloneNode(true));

    // Grab references to elements in the shadow DOM.
    this._slidesWrapper = this.shadowRoot.querySelector('#slidesWrapper');
    this._slidesSlot = this.shadowRoot.querySelector('#slidesSlot');
    this._paginationWrapper = this.shadowRoot.querySelector('#pagination');
    this._navigationWrapper = this.shadowRoot.querySelector('#navigation');
    this._prevButton = undefined;
    this._nextButton = undefined;
  }

  /**
   * Fires when the element is inserted into the DOM.
   * It's a good place to set the initial `role`, `tabindex`, internal state,
   * and install event listeners.
   */
  connectedCallback() {
    // Get Light Dom.
    this._slides = this._getSlides();

    // Setup the component.
    this._upgradeProperty('selected');
    this._upgradeProperty('loop');
    this._upgradeProperty('navigation');

    this._slideTo(this.selected);
    this._updatePagination();
    this._updateNavigation();

    // Enable transitions only after the initial setup.
    // Double rAF is necessary to wait for 'selected' to take effect.
    requestAnimationFrame(_ => {
      requestAnimationFrame(_ => {
        this.setAttribute('transitioning', '');
      });
    });

    // Add event listeners.
    this._slidesSlot.addEventListener('slotchange', this);
  }

  /**
   * Defining handleEvent allows to pass `this` as the callback to every
   * `addEventListener` and `removeEventListener`. This avoids the need of
   * binding every function. See
   * https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38
   *
   * @param {Event} e Any event.
   */
  handleEvent(e) {
    if (e.target.name === 'x-slider-pagination') {
      this._onPaginationChange(e);
    } else if (e.target === this._slidesSlot) {
      this._onSlotChange();
    } else if (this.navigation && e.target === this._prevButton) {
      this.previous();
    } else if (this.navigation && e.target === this._nextButton) {
      this.next();
    }
  }

  /**
   * Fires when the element is removed from the DOM.
   * It's a good place to do clean up work like releasing references and
   * removing event listeners.
   */
  disconnectedCallback() {
    this._slidesSlot.removeEventListener('slotchange', this);

    if (this.navigation) {
      this._prevButton.removeEventListener('click', this);
      this._nextButton.removeEventListener('click', this);
    }

    const pagination = this.shadowRoot
        .querySelectorAll('input[name="x-slider-pagination"]');
    pagination.forEach(p => {
      p.removeEventListener('change', this);
    });
  }

  /**
   * Used for upgrading properties in case this element is upgraded lazily.
   * See web/fundamentals/architecture/building-components/best-practices#lazy-properties
   * @param {any} prop
   */
  _upgradeProperty(prop) {
    if (this.hasOwnProperty(prop)) {
      let value = this[prop];
      delete this[prop];
      this[prop] = value;
    }
  }

  attributeChangedCallback(name, oldValue, newValue) {
    switch (name) {
      case 'selected':
        if (!this._slides || this._slides.length === 0) {
          return;
        }

        const parsed = parseInt(newValue, 10);

        // Accept only numbers between `0` and `this._slides.length - 1`.
        if (!Number.isFinite(parsed) ||
            parsed >= this._slides.length ||
            parsed < 0) {
          this.selected = oldValue;
          return;
        }

        // Show the new selected slide and update pagination.
        this._slideTo(parsed);
        this._updatePagination();
        this._updateNavigation();
        break;
      case 'loop':
        this._updateNavigation();
        break;
      case 'navigation':
        this._updateNavigation();
        break;
    }
  }

  /**
   * Reflects the property to its corresponding attribute.
   * @param {number} i The 0-based index of the selected slide.
   */
  set selected(index) {
    this.setAttribute('selected', index);
  }

  /**
   * Gets the property's value from the corresponding attribute.
   * @return {number} The 0-based index of the selected slide.
   */
  get selected() {
    const value = this.getAttribute('selected');
    return value === null ? 0 : parseInt(value);
  }

  /**
   * Reflects the property to its corresponding attribute.
   * @param {boolean} flag True to make the slider loop, false to disable it.
   */
  set loop(flag) {
    if (flag) {
      this.setAttribute('loop', '');
    } else {
      this.removeAttribute('loop');
    }
  }

  /**
   * Gets the property's value from the corresponding attribute.
   * @return {boolean} True if the slider is looping, false otherwise.
   */
  get loop() {
    return this.hasAttribute('loop');
  }

  /**
   * Reflects the property to its corresponding attribute.
   * @param {boolean} flag True to show navigation buttons, false to disable it.
   */
  set navigation(flag) {
    if (flag) {
      this.setAttribute('navigation', '');
    } else {
      this.removeAttribute('navigation');
    }
  }

  /**
   * Gets the property's value from the corresponding attribute.
   * @return {boolean} True if navigation buttons are shown, false otherwise.
   */
  get navigation() {
    return this.hasAttribute('navigation');
  }

  /**
   * Called when the content of #slidesSlot changes.
   * Updates the slider to the new number of slides.
   */
  _onSlotChange() {
    this._slides = this._getSlides();

    if (this.selected >= this._slides.length) {
      this.selected = this._slides.length - 1;
    }

    this._slideTo(this.selected);
    this._updatePagination();
    this._updateNavigation();
  }

  /**
   * Updates the pagination to relect the current number of slides,
   * and highlights the bullet point correponsing to the selected slide.
   */
  _updatePagination() {
    if (!this._paginationWrapper || !this._slides ||
        this._slides.length === 0) {
      return;
    }

    const currentSlide = this.selected;

    // Update the number of bullet points
    if (this._paginationWrapper.childElementCount !== this._slides.length) {
      // Remove bullet points
      // TODO: optimise by:
      // - keeping a reference to the radio buttons (useful for removing ev listeners)
      // - add/remove only what necessary
      while (this._paginationWrapper.firstChild) {
        this._paginationWrapper.firstChild.removeEventListener('change', this);
        this._paginationWrapper.removeChild(this._paginationWrapper.firstChild);
      }
      // Add bullet points.
      const frag = document.createDocumentFragment();
      this._slides.forEach((s, i) => {
        const radio = document.createElement('input');
        radio.setAttribute('type', 'radio');
        radio.setAttribute('name', 'x-slider-pagination');
        radio.setAttribute('value', i);
        radio.setAttribute('aria-label', `Go to view ${i + 1}`);
        radio.checked = i === currentSlide;
        radio.addEventListener('change', this);

        frag.appendChild(radio);
      });
      this._paginationWrapper.appendChild(frag);
    } else {
      Array.from(this._paginationWrapper.childNodes).forEach((radio, i) => {
        radio.checked = i === currentSlide;
      });
    }
  }

  _updateNavigation() {
    if (!this._navigationWrapper || !this._slides ||
        this._slides.length === 0) {
      return;
    }

    if (!this.navigation ||
        (this.navigation && this._navigationWrapper.childElementCount !== 2)) {
      // remove all children of nav wrapper and their ev listeners
      while (this._navigationWrapper.firstChild) {
        this._navigationWrapper.firstChild.removeEventListener('click', this);
        this._navigationWrapper.removeChild(this._navigationWrapper.firstChild);

        this._prevButton = undefined;
        this._nextButton = undefined;
      }
    }

    if (this.navigation) {
      if (this._navigationWrapper.childElementCount !== 2) {
        // add buttons and add ev listeners
        this._prevButton = document.createElement('button');
        this._prevButton.setAttribute('aria-label', 'To previous view');
        this._prevButton.setAttribute('id', 'previous');
        this._prevButton.textContent = '<';
        this._prevButton.addEventListener('click', this);
        this._navigationWrapper.appendChild(this._prevButton);

        this._nextButton = document.createElement('button');
        this._nextButton.setAttribute('aria-label', 'To next view');
        this._nextButton.setAttribute('id', 'next');
        this._nextButton.textContent = '>';
        this._nextButton.addEventListener('click', this);
        this._navigationWrapper.appendChild(this._nextButton);
      }

      // update disabled
      if (!this.loop && this.selected === 0) {
        this._prevButton.setAttribute('disabled', '');
      } else {
        this._prevButton.removeAttribute('disabled');
      }
      if (!this.loop && this.selected === this._slides.length - 1) {
        this._nextButton.setAttribute('disabled', '');
      } else {
        this._nextButton.removeAttribute('disabled');
      }
    }
  }

  /**
   * Called when any pagination bullet point is selected.
   * @param {Event} e The 'change' event fired by the radio input.
   */
  _onPaginationChange(e) {
    this.selected = e.target.getAttribute('value');
  }

  /**
   * Gets the elements in the light DOM (assignedNodes() of #slidesSlot).
   * @returns {Array<HTMLElement>} The elements found in #slidesSlot.
   */
  _getSlides() {
    return this._slidesSlot.assignedNodes()
        .filter(n => n.nodeType === Node.ELEMENT_NODE);
  }

  /**
   * Selects the slide preceding the currently selected one.
   * If the currently selected slide is the first slide and the loop
   * functionality is disabled, nothing happens.
   */
  previous() {
    if (this.selected > 0) {
      this.selected -= 1;
    } else if (this.loop) {
      this.selected = this._slides.length - 1;
    }
  }

  /**
   * Selects the slide following the currently selected one.
   * If the currently selected slide is the last slide and the loop
   * functionality is disabled, nothing happens.
   */
  next() {
    if (this.selected < this._slides.length - 1) {
      this.selected += 1;
    } else if (this.loop) {
      this.selected = 0;
    }
  }

  /**
   * Translates the slider to show the target slide.
   * @param {number} targetSlide The slide to slide to.
   */
  _slideTo(targetSlide) {
    if (!this._slidesWrapper) {
      return;
    }

    this._slidesWrapper.style.transform = `translateX(${- targetSlide * 100}%)`;
  }
}

window.customElements.define('x-slider', XSlider);
})();
<commit_msg>refactor(navigation): simpler `disabled` computation<commit_after>(function() {
/**
 * Markup and styles.
 */
const template = document.createElement('template');
template.innerHTML = `

      <slot id="slidesSlot">
No content available
</slot>

`;

/**
 * XSlider definition.
 */
class XSlider extends HTMLElement {
  static get observedAttributes() {
    return ['selected', 'loop', 'navigation'];
  }

  /**
   * Runs anytime a new instance is created (in HTML or JS).
   * The construtor is a good place to create shadow DOM, though you should
   * avoid touching any attributes or light DOM children as they may not
   * be available yet.
   * @constructor
   */
  constructor() {
    super();

    this.attachShadow({mode: 'open'});
    this.shadowRoot.appendChild(template.content.cloneNode(true));

    // Grab references to elements in the shadow DOM.
    this._slidesWrapper = this.shadowRoot.querySelector('#slidesWrapper');
    this._slidesSlot = this.shadowRoot.querySelector('#slidesSlot');
    this._paginationWrapper = this.shadowRoot.querySelector('#pagination');
    this._navigationWrapper = this.shadowRoot.querySelector('#navigation');
    this._prevButton = undefined;
    this._nextButton = undefined;
  }

  /**
   * Fires when the element is inserted into the DOM.
   * It's a good place to set the initial `role`, `tabindex`, internal state,
   * and install event listeners.
   */
  connectedCallback() {
    // Get Light Dom.
    this._slides = this._getSlides();

    // Setup the component.
    this._upgradeProperty('selected');
    this._upgradeProperty('loop');
    this._upgradeProperty('navigation');

    this._slideTo(this.selected);
    this._updatePagination();
    this._updateNavigation();

    // Enable transitions only after the initial setup.
    // Double rAF is necessary to wait for 'selected' to take effect.
    requestAnimationFrame(_ => {
      requestAnimationFrame(_ => {
        this.setAttribute('transitioning', '');
      });
    });

    // Add event listeners.
    this._slidesSlot.addEventListener('slotchange', this);
  }

  /**
   * Defining handleEvent allows to pass `this` as the callback to every
   * `addEventListener` and `removeEventListener`. This avoids the need of
   * binding every function. See
   * https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38
   *
   * @param {Event} e Any event.
   */
  handleEvent(e) {
    if (e.target.name === 'x-slider-pagination') {
      this._onPaginationChange(e);
    } else if (e.target === this._slidesSlot) {
      this._onSlotChange();
    } else if (this.navigation && e.target === this._prevButton) {
      this.previous();
    } else if (this.navigation && e.target === this._nextButton) {
      this.next();
    }
  }

  /**
   * Fires when the element is removed from the DOM.
   * It's a good place to do clean up work like releasing references and
   * removing event listeners.
   */
  disconnectedCallback() {
    this._slidesSlot.removeEventListener('slotchange', this);

    if (this.navigation) {
      this._prevButton.removeEventListener('click', this);
      this._nextButton.removeEventListener('click', this);
    }

    const pagination = this.shadowRoot
        .querySelectorAll('input[name="x-slider-pagination"]');
    pagination.forEach(p => {
      p.removeEventListener('change', this);
    });
  }

  /**
   * Used for upgrading properties in case this element is upgraded lazily.
   * See web/fundamentals/architecture/building-components/best-practices#lazy-properties
   * @param {any} prop
   */
  _upgradeProperty(prop) {
    if (this.hasOwnProperty(prop)) {
      let value = this[prop];
      delete this[prop];
      this[prop] = value;
    }
  }

  attributeChangedCallback(name, oldValue, newValue) {
    switch (name) {
      case 'selected':
        if (!this._slides || this._slides.length === 0) {
          return;
        }

        const parsed = parseInt(newValue, 10);

        // Accept only numbers between `0` and `this._slides.length - 1`.
        if (!Number.isFinite(parsed) ||
            parsed >= this._slides.length ||
            parsed < 0) {
          this.selected = oldValue;
          return;
        }

        // Show the new selected slide and update pagination.
        this._slideTo(parsed);
        this._updatePagination();
        this._updateNavigation();
        break;
      case 'loop':
        this._updateNavigation();
        break;
      case 'navigation':
        this._updateNavigation();
        break;
    }
  }

  /**
   * Reflects the property to its corresponding attribute.
   * @param {number} i The 0-based index of the selected slide.
   */
  set selected(index) {
    this.setAttribute('selected', index);
  }

  /**
   * Gets the property's value from the corresponding attribute.
   * @return {number} The 0-based index of the selected slide.
   */
  get selected() {
    const value = this.getAttribute('selected');
    return value === null ? 0 : parseInt(value);
  }

  /**
   * Reflects the property to its corresponding attribute.
   * @param {boolean} flag True to make the slider loop, false to disable it.
   */
  set loop(flag) {
    if (flag) {
      this.setAttribute('loop', '');
    } else {
      this.removeAttribute('loop');
    }
  }

  /**
   * Gets the property's value from the corresponding attribute.
   * @return {boolean} True if the slider is looping, false otherwise.
   */
  get loop() {
    return this.hasAttribute('loop');
  }

  /**
   * Reflects the property to its corresponding attribute.
   * @param {boolean} flag True to show navigation buttons, false to disable it.
   */
  set navigation(flag) {
    if (flag) {
      this.setAttribute('navigation', '');
    } else {
      this.removeAttribute('navigation');
    }
  }

  /**
   * Gets the property's value from the corresponding attribute.
   * @return {boolean} True if navigation buttons are shown, false otherwise.
   */
  get navigation() {
    return this.hasAttribute('navigation');
  }

  /**
   * Called when the content of #slidesSlot changes.
   * Updates the slider to the new number of slides.
   */
  _onSlotChange() {
    this._slides = this._getSlides();

    if (this.selected >= this._slides.length) {
      this.selected = this._slides.length - 1;
    }

    this._slideTo(this.selected);
    this._updatePagination();
    this._updateNavigation();
  }

  /**
   * Updates the pagination to relect the current number of slides,
   * and highlights the bullet point correponsing to the selected slide.
   */
  _updatePagination() {
    if (!this._paginationWrapper || !this._slides ||
        this._slides.length === 0) {
      return;
    }

    const currentSlide = this.selected;

    // Update the number of bullet points
    if (this._paginationWrapper.childElementCount !== this._slides.length) {
      // Remove bullet points
      // TODO: optimise by:
      // - keeping a reference to the radio buttons (useful for removing ev listeners)
      // - add/remove only what necessary
      while (this._paginationWrapper.firstChild) {
        this._paginationWrapper.firstChild.removeEventListener('change', this);
        this._paginationWrapper.removeChild(this._paginationWrapper.firstChild);
      }
      // Add bullet points.
      const frag = document.createDocumentFragment();
      this._slides.forEach((s, i) => {
        const radio = document.createElement('input');
        radio.setAttribute('type', 'radio');
        radio.setAttribute('name', 'x-slider-pagination');
        radio.setAttribute('value', i);
        radio.setAttribute('aria-label', `Go to view ${i + 1}`);
        radio.checked = i === currentSlide;
        radio.addEventListener('change', this);

        frag.appendChild(radio);
      });
      this._paginationWrapper.appendChild(frag);
    } else {
      Array.from(this._paginationWrapper.childNodes).forEach((radio, i) => {
        radio.checked = i === currentSlide;
      });
    }
  }

  _updateNavigation() {
    if (!this._navigationWrapper || !this._slides ||
        this._slides.length === 0) {
      return;
    }

    if (!this.navigation ||
        (this.navigation && this._navigationWrapper.childElementCount !== 2)) {
      // remove all children of nav wrapper and their ev listeners
      while (this._navigationWrapper.firstChild) {
        this._navigationWrapper.firstChild.removeEventListener('click', this);
        this._navigationWrapper.removeChild(this._navigationWrapper.firstChild);

        this._prevButton = undefined;
        this._nextButton = undefined;
      }
    }

    if (this.navigation) {
      if (this._navigationWrapper.childElementCount !== 2) {
        // add buttons and add ev listeners
        this._prevButton = document.createElement('button');
        this._prevButton.setAttribute('aria-label', 'To previous view');
        this._prevButton.setAttribute('id', 'previous');
        this._prevButton.textContent = '<';
        this._prevButton.addEventListener('click', this);
        this._navigationWrapper.appendChild(this._prevButton);

        this._nextButton = document.createElement('button');
        this._nextButton.setAttribute('aria-label', 'To next view');
        this._nextButton.setAttribute('id', 'next');
        this._nextButton.textContent = '>';
        this._nextButton.addEventListener('click', this);
        this._navigationWrapper.appendChild(this._nextButton);
      }

      // update `disabled`
      this._prevButton.disabled =
          !this.loop && this.selected === 0;
      this._nextButton.disabled =
          !this.loop && this.selected === this._slides.length - 1;
    }
  }

  /**
   * Called when any pagination bullet point is selected.
   * @param {Event} e The 'change' event fired by the radio input.
   */
  _onPaginationChange(e) {
    this.selected = e.target.getAttribute('value');
  }

  /**
   * Gets the elements in the light DOM (assignedNodes() of #slidesSlot).
   * @returns {Array<HTMLElement>} The elements found in #slidesSlot.
   */
  _getSlides() {
    return this._slidesSlot.assignedNodes()
        .filter(n => n.nodeType === Node.ELEMENT_NODE);
  }

  /**
   * Selects the slide preceding the currently selected one.
   * If the currently selected slide is the first slide and the loop
   * functionality is disabled, nothing happens.
   */
  previous() {
    if (this.selected > 0) {
      this.selected -= 1;
    } else if (this.loop) {
      this.selected = this._slides.length - 1;
    }
  }

  /**
   * Selects the slide following the currently selected one.
   * If the currently selected slide is the last slide and the loop
   * functionality is disabled, nothing happens.
   */
  next() {
    if (this.selected < this._slides.length - 1) {
      this.selected += 1;
    } else if (this.loop) {
      this.selected = 0;
    }
  }

  /**
   * Translates the slider to show the target slide.
   * @param {number} targetSlide The slide to slide to.
   */
  _slideTo(targetSlide) {
    if (!this._slidesWrapper) {
      return;
    }

    this._slidesWrapper.style.transform = `translateX(${- targetSlide * 100}%)`;
  }
}

window.customElements.define('x-slider', XSlider);
})();
<|endoftext|>


<commit_before>var async = require('async');
var _ = require('underscore');
var extend = require('extend');
var snippets = require('apostrophe-snippets');
var util = require('util');
var moment = require('moment');

// Creating an instance of the blog module is easy:
// var blog = require('apostrophe-blog')(options, callback);
//
// If you want to access the constructor function for use in the
// constructor of a module that extends this one, consider:
//
// var blog = require('apostrophe-blog');
// ... Inside the constructor for the new object ...
// blog.Blog.call(this, options, null);
//
// In fact, this module does exactly that to extend the snippets module
// (see below). Something similar happens on the browser side in
// main.js.

module.exports = blog;

function blog(options, callback) {
  return new blog.Blog(options, callback);
}

blog.Blog = function(options, callback) {
  var self = this;
  _.defaults(options, {
    instance: 'blogPost',
    name: options.name || 'blog',
    label: options.name || 'Blog',
    icon: options.icon || 'blog',
    // The default would be aposBlogPostMenu, this is more natural
    menuName: 'aposBlogMenu'
  });

  options.modules = (options.modules || []).concat([ { dir: __dirname, name: 'blog' } ]);

  // Call the base class constructor. Don't pass the callback, we want to invoke it
  // ourselves after constructing more stuff
  snippets.Snippets.call(this, options, null);

  // The snippet dispatcher is almost perfect for our needs, except that
  // we expect the publication date of the blog post to appear before the slug
  // of the blog post in the URL. So spot that situation, change req.remainder
  // to just the slug of the blog post, and invoke the original version of
  // "dispatch."

  // Grab the "superclass" version of the dispatch method so we can call it
  var superDispatch = self.dispatch;

  self.dispatch = function(req, callback) {
    if (req.remainder.length) {
      var matches = req.remainder.match(/^\/\d+\/\d+\/\d+\/(.*)$/);
      if (matches) {
        req.remainder = '/' + matches[1];
      }
    }
    superDispatch.call(this, req, callback);
  };

  self.getDefaultTitle = function() {
    return 'My Article';
  };

  // TODO this is not very i18n friendly
  self.getAutocompleteTitle = function(snippet) {
    return snippet.title + ' (' + moment(snippet.publishedAt).format('MM/DD') + ')';
  };

  // I bet you want some extra fields available along with the title to go with
  // your custom getAutocompleteTitle. Override this to retrieve more stuff.
  // We keep it to a minimum for performance.
  self.getAutocompleteFields = function() {
    return { title: 1, _id: 1, publishedAt: 1 };
  };

  // Returns a "permalink" URL to the snippet, beginning with the
  // slug of the specified page. See findBestPage for a good way to
  // choose a page beneath which to link this snippet.
  //
  // It is commonplace to override this function. For instance,
  // blog posts add the publication date to the URL.

  self.permalink = function(snippet, page) {
    return page.slug + '/' + moment(snippet.publishedAt).format('YYYY/MM/DD') + '/' + snippet.slug;
  };

  // Establish the default sort order for blogs
  var superGet = self.get;

  self.get = function(req, optionsArg, callback) {
    var options = {};

    // "Why copy the object like this?" If we don't, we're modifying the
    // object that was passed to us, which could lead to side effects
    extend(options, optionsArg || {}, true);

    // If options.publishedAt is 'any', we're in the admin interface and should be
    // able to see articles whose publication date has not yet arrived. Otherwise,
    // show only published stuff
    if (options.publishedAt === 'any') {
      delete options.publishedAt;
    } else if (!options.publishedAt) {
      options.publishedAt = { $lte: new Date() };
    } else {
      // Custom criteria were passed for publishedAt
    }

    if (!options.sort) {
      options.sort = { publishedAt: -1 };
    }
    return superGet.call(self, req, options, callback);
  };

  function appendExtraFields(data, snippet, callback) {
    snippet.publicationDate = self._apos.sanitizeDate(data.publicationDate, snippet.publicationDate);
    snippet.publicationTime = self._apos.sanitizeTime(data.publicationTime, snippet.publicationTime);
    if (snippet.publicationTime === null) {
      snippet.publishedAt = new Date(snippet.publicationDate);
    } else {
      snippet.publishedAt = new Date(snippet.publicationDate + ' ' + snippet.publicationTime);
    }
    return callback(null);
  }

  self.beforeInsert = function(req, data, snippet, callback) {
    appendExtraFields(data, snippet, callback);
  };

  self.beforeUpdate = function(req, data, snippet, callback) {
    appendExtraFields(data, snippet, callback);
  };

  var superAddApiCriteria = self.addApiCriteria;
  self.addApiCriteria = function(query, criteria) {
    superAddApiCriteria.call(self, query, criteria);
    criteria.publishedAt = 'any';
  };

  var superAddExtraAutocompleteCriteria = self.addExtraAutocompleteCriteria;
  self.addExtraAutocompleteCriteria = function(req, criteria) {
    superAddExtraAutocompleteCriteria.call(self, req, criteria);
    criteria.publishedAt = 'any';
  };

  self._apos.tasks['generate-blog-posts'] = function(callback) {
    var randomWords = require('random-words');
    var i;
    var posts = [];
    for (i = 0; (i < 100); i++) {
      var title = randomWords({ min: 5, max: 10, join: ' ' });
      var at = new Date();
      posts.push({
        type: 'blogPost',
        title: title,
        slug: self._apos.slugify(title),
        areas: {
          body: {
            items: [
              {
                type: 'richText',
                content: randomWords({ min: 50, max: 200, join: ' ' })
              }
            ]
          }
        },
        publishedAt: at,
        publicationDate: moment(at).format('YYYY-MM-DD'),
        publicationTime: moment(at).format('HH:MM'),
        published: true
      });
    }
    self._apos.pages.insert(posts, callback);
  };

  if (callback) {
    // Invoke callback on next tick so that the blog object
    // is returned first and can be assigned to a variable for
    // use in whatever our callback is invoking
    process.nextTick(function() { return callback(null); });
  }
};

<commit_msg>* Compatible with new apos.get<commit_after>var async = require('async');
var _ = require('underscore');
var extend = require('extend');
var snippets = require('apostrophe-snippets');
var util = require('util');
var moment = require('moment');

// Creating an instance of the blog module is easy:
// var blog = require('apostrophe-blog')(options, callback);
//
// If you want to access the constructor function for use in the
// constructor of a module that extends this one, consider:
//
// var blog = require('apostrophe-blog');
// ... Inside the constructor for the new object ...
// blog.Blog.call(this, options, null);
//
// In fact, this module does exactly that to extend the snippets module
// (see below). Something similar happens on the browser side in
// main.js.

module.exports = blog;

function blog(options, callback) {
  return new blog.Blog(options, callback);
}

blog.Blog = function(options, callback) {
  var self = this;
  _.defaults(options, {
    instance: 'blogPost',
    name: options.name || 'blog',
    label: options.name || 'Blog',
    icon: options.icon || 'blog',
    // The default would be aposBlogPostMenu, this is more natural
    menuName: 'aposBlogMenu'
  });

  options.modules = (options.modules || []).concat([ { dir: __dirname, name: 'blog' } ]);

  // Call the base class constructor. Don't pass the callback, we want to invoke it
  // ourselves after constructing more stuff
  snippets.Snippets.call(this, options, null);

  // The snippet dispatcher is almost perfect for our needs, except that
  // we expect the publication date of the blog post to appear before the slug
  // of the blog post in the URL. So spot that situation, change req.remainder
  // to just the slug of the blog post, and invoke the original version of
  // "dispatch."

  // Grab the "superclass" version of the dispatch method so we can call it
  var superDispatch = self.dispatch;

  self.dispatch = function(req, callback) {
    if (req.remainder.length) {
      var matches = req.remainder.match(/^\/\d+\/\d+\/\d+\/(.*)$/);
      if (matches) {
        req.remainder = '/' + matches[1];
      }
    }
    superDispatch.call(this, req, callback);
  };

  self.getDefaultTitle = function() {
    return 'My Article';
  };

  // TODO this is not very i18n friendly
  self.getAutocompleteTitle = function(snippet) {
    return snippet.title + ' (' + moment(snippet.publishedAt).format('MM/DD') + ')';
  };

  // I bet you want some extra fields available along with the title to go with
  // your custom getAutocompleteTitle. Override this to retrieve more stuff.
  // We keep it to a minimum for performance.
  self.getAutocompleteFields = function() {
    return { title: 1, _id: 1, publishedAt: 1 };
  };

  // Returns a "permalink" URL to the snippet, beginning with the
  // slug of the specified page. See findBestPage for a good way to
  // choose a page beneath which to link this snippet.
  //
  // It is commonplace to override this function. For instance,
  // blog posts add the publication date to the URL.

  self.permalink = function(snippet, page) {
    return page.slug + '/' + moment(snippet.publishedAt).format('YYYY/MM/DD') + '/' + snippet.slug;
  };

  // Establish the default sort order for blogs
  var superGet = self.get;

  self.get = function(req, userCriteria, optionsArg, callback) {
    var options = {};
    var filterCriteria = {};

    // "Why copy the object like this?" If we don't, we're modifying the
    // object that was passed to us, which could lead to side effects
    extend(options, optionsArg || {}, true);

    // If options.publishedAt is 'any', we're in the admin interface and should be
    // able to see articles whose publication date has not yet arrived. Otherwise,
    // show only published stuff
    if (options.publishedAt === 'any') {
      // Do not add our usual criteria for publication date. Note
      // that userCriteria may still examine publication date
    } else {
      filterCriteria.publishedAt = { $lte: new Date() };
    }

    if (!options.sort) {
      options.sort = { publishedAt: -1 };
    }

    // Use $and to build a query that preserves whatever the
    // userCriteria may be trying to do while still enforcing
    // our publication date criteria

    return superGet.call(self, req, { $and: [ userCriteria, filterCriteria ] }, options, callback);
  };

  function appendExtraFields(data, snippet, callback) {
    snippet.publicationDate = self._apos.sanitizeDate(data.publicationDate, snippet.publicationDate);
    snippet.publicationTime = self._apos.sanitizeTime(data.publicationTime, snippet.publicationTime);
    if (snippet.publicationTime === null) {
      snippet.publishedAt = new Date(snippet.publicationDate);
    } else {
      snippet.publishedAt = new Date(snippet.publicationDate + ' ' + snippet.publicationTime);
    }
    return callback(null);
  }

  self.beforeInsert = function(req, data, snippet, callback) {
    appendExtraFields(data, snippet, callback);
  };

  self.beforeUpdate = function(req, data, snippet, callback) {
    appendExtraFields(data, snippet, callback);
  };

  var superAddApiCriteria = self.addApiCriteria;
  self.addApiCriteria = function(query, criteria, options) {
    superAddApiCriteria.call(self, query, criteria, options);
    options.publishedAt = 'any';
  };

  var superAddExtraAutocompleteCriteria = self.addExtraAutocompleteCriteria;
  self.addExtraAutocompleteCriteria = function(req, criteria, options) {
    superAddExtraAutocompleteCriteria.call(self, req, criteria, options);
    options.publishedAt = 'any';
  };

  self._apos.tasks['generate-blog-posts'] = function(callback) {
    var randomWords = require('random-words');
    var i;
    var posts = [];
    for (i = 0; (i < 100); i++) {
      var title = randomWords({ min: 5, max: 10, join: ' ' });
      var at = new Date();
      posts.push({
        type: 'blogPost',
        title: title,
        slug: self._apos.slugify(title),
        testData: true,
        areas: {
          body: {
            items: [
              {
                type: 'richText',
                content: randomWords({ min: 50, max: 200, join: ' ' })
              }
            ]
          }
        },
        publishedAt: at,
        publicationDate: moment(at).format('YYYY-MM-DD'),
        publicationTime: moment(at).format('HH:MM'),
        published: true
      });
    }
    self._apos.pages.insert(posts, callback);
  };

  if (callback) {
    // Invoke callback on next tick so that the blog object
    // is returned first and can be assigned to a variable for
    // use in whatever our callback is invoking
    process.nextTick(function() { return callback(null); });
  }
};

<|endoftext|>


<commit_before>angular.module('opal.controllers').controller(
    'ExtractCtrl', function($scope, $http, $window, $modal,
                            filters, options, schema){

        $scope.JSON = window.JSON;
        $scope.state =  'normal';
        $scope.filters = filters;
        $scope.columns = schema.columns;
        $scope.column_names = _.map(schema.columns, function(c){
            return c.name.underscoreToCapWords();
        });

	    for (var name in options) {
		    $scope[name + '_list'] = options[name];
	    };

        $scope.model = {
            combine    : "and",
            column     : null,
            field      : null,
            queryType  : "Equals",
            query      : null,
            lookup_list: []
        };

        $scope.criteria = [_.clone($scope.model)];

        $scope.completeCriteria =  function(){
            return _.filter($scope.criteria, function(c){
                if(c.column &&  c.field &&  c.query){
                    return true
                }
                return false
            })
        };

        $scope.searchableFields = function(column){
            // TODO - don't hard-code this
            if(column.name == 'microbiology_test'){
                return [
                    'Test',
                    'Date Ordered',
                    'Details',
                    'Microscopy',
                    'Organism',
                    'Sensitive Antibiotics',
                    'Resistant Antibiotics'
                ]
            }
            return _.map(
                _.reject(
                    column.fields,
                    function(c){ return c.type == 'token' ||  c.type ==  'list'; }),
                function(c){ return c.name.underscoreToCapWords(); }
            );
        };

        $scope.isType = function(column, field, type){
            if(!column || !field){
                return false;
            }
            var col = _.find($scope.columns, function(item){return item.name == column.toLowerCase().replace( / /g,  '_')});
            var theField =  _.find(col.fields, function(f){return f.name == field.toLowerCase().replace( / /g,  '_')});
            if(!theField){ return false }
            return theField.type == type;
        };

        $scope.isBoolean = function(column, field){
            return $scope.isType(column, field, "boolean");
        };

        $scope.isText = function(column, field){
            return $scope.isType(column, field, "string") || $scope.isType(column, field, "text");
        }

        $scope.isDate = function(column, field){
            return $scope.isType(column, field, "date");
        };

        $scope.addFilter = function(){
            $scope.criteria.push(_.clone($scope.model));
        };

        $scope.removeFilter = function(index){
            $scope.criteria.splice(index, 1);
        };

        //
        // Determine the appropriate lookup list for this field if
        // one exists.
        //
       $scope.$watch('criteria', function(){
            _.map($scope.criteria, function(c){
                var column = _.findWhere($scope.columns, {name: c.column});
                if(!column){return}
                if(!c.field){return}
                var field = _.findWhere(column.fields, {name: c.field.toLowerCase().replace(/ /g, '_')});
                if(!field){return}
                if(field.lookup_list){
                    c.lookup_list = $scope[field.lookup_list + '_list'];
                }
            });
        }, true);

        // $scope.geLookupValues = function(query){
        //     var column = _.findWhere($scope.columns, {name: c.column});
        //     if(!column){return []}
        //     if(!c.field){return []}
        //     var field = _.findWhere(column.fields, {name: c.field.toLowerCase()});
        //     if(field.lookup_list){
        //         return $scope[field.lookup_list + '_list'];
        //     }else{
        //         return []
        //     }
        // }

        $scope.search = function(){
            $scope.state = 'pending';
            $http.post('/search/extract/', $scope.completeCriteria()).success(
                function(results){
                    $scope.results = results;
                    $scope.state = 'normal';
                }).error(function(){
                    $scope.state = 'normal';
                    $window.alert('ERROR: Could not process this search. Please report it to the OPAL team')
                });
        };

        $scope.jumpToFilter = function($event, filter){
            $event.preventDefault()
            $scope.criteria = filter.criteria;
        }

        $scope.editFilter = function($event, filter, $index){
            $event.preventDefault();
		    modal = $modal.open({
			    templateUrl: '/templates/modals/save_filter_modal.html/',
			    controller: 'SaveFilterCtrl',
			    resolve: {
				    params: function() { return $scope.filters[$index]; },
			    }
		    }).result.then(function(result){
                $scope.filters[$index] = result;
            });
        }

        $scope.save = function(){

		    modal = $modal.open({
			    templateUrl: '/templates/modals/save_filter_modal.html/',
			    controller: 'SaveFilterCtrl',
			    resolve: {
				    params: function() { return {name: null, criteria: $scope.criteria}; },
			    }
		    }).result.then(function(result){
                $scope.filters.push(result);
            });
        };

    });
<commit_msg>Only save complete search criteria. refs openhealthcare/elcid#320<commit_after>angular.module('opal.controllers').controller(
    'ExtractCtrl', function($scope, $http, $window, $modal,
                            filters, options, schema){

        $scope.JSON = window.JSON;
        $scope.state =  'normal';
        $scope.filters = filters;
        $scope.columns = schema.columns;
        $scope.column_names = _.map(schema.columns, function(c){
            return c.name.underscoreToCapWords();
        });

	    for (var name in options) {
		    $scope[name + '_list'] = options[name];
	    };

        $scope.model = {
            combine    : "and",
            column     : null,
            field      : null,
            queryType  : "Equals",
            query      : null,
            lookup_list: []
        };

        $scope.criteria = [_.clone($scope.model)];

        $scope.completeCriteria =  function(){
            return _.filter($scope.criteria, function(c){
                if(c.column &&  c.field &&  c.query){
                    return true
                }
                return false
            })
        };

        $scope.searchableFields = function(column){
            // TODO - don't hard-code this
            if(column.name == 'microbiology_test'){
                return [
                    'Test',
                    'Date Ordered',
                    'Details',
                    'Microscopy',
                    'Organism',
                    'Sensitive Antibiotics',
                    'Resistant Antibiotics'
                ]
            }
            return _.map(
                _.reject(
                    column.fields,
                    function(c){ return c.type == 'token' ||  c.type ==  'list'; }),
                function(c){ return c.name.underscoreToCapWords(); }
            );
        };

        $scope.isType = function(column, field, type){
            if(!column || !field){
                return false;
            }
            var col = _.find($scope.columns, function(item){return item.name == column.toLowerCase().replace( / /g,  '_')});
            var theField =  _.find(col.fields, function(f){return f.name == field.toLowerCase().replace( / /g,  '_')});
            if(!theField){ return false }
            return theField.type == type;
        };

        $scope.isBoolean = function(column, field){
            return $scope.isType(column, field, "boolean");
        };

        $scope.isText = function(column, field){
            return $scope.isType(column, field, "string") || $scope.isType(column, field, "text");
        }

        $scope.isDate = function(column, field){
            return $scope.isType(column, field, "date");
        };

        $scope.addFilter = function(){
            $scope.criteria.push(_.clone($scope.model));
        };

        $scope.removeFilter = function(index){
            $scope.criteria.splice(index, 1);
        };

        //
        // Determine the appropriate lookup list for this field if
        // one exists.
        //
       $scope.$watch('criteria', function(){
            _.map($scope.criteria, function(c){
                var column = _.findWhere($scope.columns, {name: c.column});
                if(!column){return}
                if(!c.field){return}
                var field = _.findWhere(column.fields, {name: c.field.toLowerCase().replace(/ /g, '_')});
                if(!field){return}
                if(field.lookup_list){
                    c.lookup_list = $scope[field.lookup_list + '_list'];
                }
            });
        }, true);

        $scope.search = function(){
            $scope.state = 'pending';
            $http.post('/search/extract/', $scope.completeCriteria()).success(
                function(results){
                    $scope.results = results;
                    $scope.state = 'normal';
                }).error(function(){
                    $scope.state = 'normal';
                    $window.alert('ERROR: Could not process this search. Please report it to the OPAL team')
                });
        };

        $scope.jumpToFilter = function($event, filter){
            $event.preventDefault()
            $scope.criteria = filter.criteria;
        }

        $scope.editFilter = function($event, filter, $index){
            $event.preventDefault();
		    modal = $modal.open({
			    templateUrl: '/templates/modals/save_filter_modal.html/',
			    controller: 'SaveFilterCtrl',
			    resolve: {
				    params: function() { return $scope.filters[$index]; },
			    }
		    }).result.then(function(result){
                $scope.filters[$index] = result;
            });
        }

        $scope.save = function(){

		    modal = $modal.open({
			    templateUrl: '/templates/modals/save_filter_modal.html/',
			    controller: 'SaveFilterCtrl',
			    resolve: {
				    params: function() { return {name: null, criteria: $scope.completeCriteria()}; },
			    }
		    }).result.then(function(result){
                $scope.filters.push(result);
            });
        };

    });
<|endoftext|>


<commit_before>var fs = require('fs')
var http = require('http')
var opn = require('opn')
var shoe = require('shoe')

module.exports = function (defaultLngLat, cb) {
  defaultLngLat = defaultLngLat || { lat: -25.363882, lng: 131.044922 }
  var pos = defaultLngLat
  var port = 9691

  // Serve the static files.
  var server = http.createServer(function(req, res) {
    if (/bundle.js/.test(req.url)) {
      fs.createReadStream('bundle.js').pipe(res)
    } else {
      fs.createReadStream('map.html').pipe(res)
    }
  })

  server.listen(port, function() {
    opn('http://localhost:' + port + '?lon=' + defaultLngLat.lng + '&lat=' + defaultLngLat.lat)
  })

  var sock = shoe(function(stream) {
    stream.on('end', function() {
      server.close()
      cb(null, pos)
    })
    stream.on('close', function() {
      server.close()
      cb(null, pos)
    })
    stream.on('data', function(data) {
      pos = JSON.parse(data.toString())
    })
  })
  sock.install(server, '/whereami')
}

<commit_msg>correct pathing for module-local files<commit_after>var fs = require('fs')
var http = require('http')
var opn = require('opn')
var path = require('path')
var shoe = require('shoe')

module.exports = function (defaultLngLat, cb) {
  defaultLngLat = defaultLngLat || { lat: -25.363882, lng: 131.044922 }
  var pos = defaultLngLat
  var port = 9691

  // Serve the static files.
  var server = http.createServer(function(req, res) {
    if (/bundle.js/.test(req.url)) {
      fs.createReadStream(path.join(__dirname, 'bundle.js')).pipe(res)
    } else {
      fs.createReadStream(path.join(__dirname, 'map.html')).pipe(res)
    }
  })

  server.listen(port, function() {
    opn('http://localhost:' + port + '?lon=' + defaultLngLat.lng + '&lat=' + defaultLngLat.lat)
  })

  var sock = shoe(function(stream) {
    stream.on('end', function() {
      server.close()
      cb(null, pos)
    })
    stream.on('close', function() {
      server.close()
      cb(null, pos)
    })
    stream.on('data', function(data) {
      pos = JSON.parse(data.toString())
    })
  })
  sock.install(server, '/whereami')
}

<|endoftext|>


<commit_before>// Initialize user character
var img = document.querySelector('.imgBox');
var avatar = document.getElementById('avatar')

// Initialize information boxes
var intro = document.querySelector('.infoBoxIntro');
var proj = document.querySelector('.infoBoxProj');
var exp = document.querySelector('.infoBoxExp');
var act = document.querySelector('.infoBoxAct');

// Initialize paragraph, mobile mode div and other misc variables
var infoPara = document.getElementById('infoPara');
var mobile = document.querySelector('.mobile');
var down = false;
var key, activeUserForward, activeUserLeft, activeUserRight, activeUserBack, topPos, leftPos, rightPos, botPos;

// Get webpage locations of info boxes and user img
var introBounds = intro.getBoundingClientRect();
var projBounds = proj.getBoundingClientRect();
var expBounds = exp.getBoundingClientRect();
var actBounds = act.getBoundingClientRect();
var imgBounds = img.getBoundingClientRect();

// Constructor for box
function Box(x1, x2, y1, y2, type) {
  this.x1 = x1;
  this.x2 = x2;
  this.y1 = y1;
  this.y2 = y2;
  this.type = type;
}
Box.prototype.crossCheck = function() {
  imgBounds = img.getBoundingClientRect();
  if ((imgBounds.top + 10 < this.y1 &&
      imgBounds.top + 10 > this.y2 &&
      imgBounds.left + 10 > this.x1 &&
      imgBounds.left + 10 < this.x2)) {
    window.location = this.type + ".html";
  }
}

// Initialize info boxes
var introBox = new Box(introBounds.left, introBounds.right, introBounds.bottom, introBounds.top, 'intro');
var projBox = new Box(projBounds.left, projBounds.right, projBounds.bottom, projBounds.top, 'proj');
var expBox = new Box(expBounds.left, expBounds.right, expBounds.bottom, expBounds.top, 'exp');
var actBox = new Box(actBounds.left, actBounds.right, actBounds.bottom, actBounds.top, 'act');

// Initialize user character movement functionality: Move
document.addEventListener('keydown', function(e) {
    key = e.keyCode;
    if (!down) {
      switch (key) {
        case 87:
          activeUserForward = setInterval(forward, 40);
          activeImage = setInterval(walkingForward, 40);
          break;
        case 65:
          activeUserLeft = setInterval(left, 40);
          activeImage = setInterval(walkingLeft, 40);
          break;
        case 68:
          activeUserRight = setInterval(right, 40);
          activeImage = setInterval(walkingRight, 40);
          break;
        case 83:
          activeUserBack = setInterval(back, 40);
          activeImage = setInterval(walkingBack, 40);
          break;
        default:
          break;
      }
      down = true;
    }
    introBox.crossCheck();
    projBox.crossCheck();
    expBox.crossCheck();
    actBox.crossCheck();
  }

)

// Initialize user character movement functionality: Stop
document.addEventListener('keyup', function(e) {
  key = e.keyCode;
  switch (key) {
    case 87:
      clearInterval(activeUserForward);
      clearInterval(activeImage);
      down = false;
      break;
    case 65:
      clearInterval(activeUserLeft);
      clearInterval(activeImage);
      down = false;
      break;
    case 68:
      clearInterval(activeUserRight);
      clearInterval(activeImage);
      down = false;
      break;
    case 83:
      clearInterval(activeUserBack);
      clearInterval(activeImage);
      down = false;
      break;
    default:
      break;
  }
clearInterval(activeUserBack);
clearInterval(activeUserForward);
clearInterval(activeUserRight);
clearInterval(activeUserLeft);
})

// Avatar image change for animation functions
function forward() {
  topPos = img.offsetTop;
  img.style.top = topPos - 6 + 'px';
}

function left() {
  leftPos = img.offsetLeft;
  img.style.left = leftPos - 6 + 'px'
}

function right() {
  leftPos = img.offsetLeft;
  img.style.left = leftPos + 6 + 'px'
}

function back() {
  topPos = img.offsetTop;
  img.style.top = topPos + 6 + 'px'
}

mobile.onclick = function() {
	intro.style.cursor="pointer";
	intro.onclick = function() {
  	window.location = "intro.html";
  }
  proj.style.cursor="pointer";
  proj.onclick = function() {
  	window.location = "proj.html";
  }
  exp.style.cursor="pointer";
  exp.onclick = function() {
  window.location = "exp.html";
  }
  act.style.cursor="pointer";
  act.onclick = function() {
  window.location = "act.html";
  }
  mobile.textContent = "Now tap on any box for more information"
}

function walkingLeft() {
  avatar.src = "left1"
  setTimeout(donothing(), 5);
  avatar.src = "left2"
  setTimeout(donothing(), 5);
  avatar.src = "left3"
  setTimeout(donothing(), 5);
}

function walkingRight() {
  avatar.src = "right1"
  setTimeout(donothing(), 5);
  avatar.src = "right2"
  setTimeout(donothing(), 5);
  avatar.src = "right3"
  setTimeout(donothing(), 5);
}

function walkingForward() {
  avatar.src = "right1"
  setTimeout(donothing(), 5);
  avatar.src = "right2"
  setTimeout(donothing(), 5);
  avatar.src = "right3"
  setTimeout(donothing(), 5);
}

function walkingBack() {
  avatar.src = "back1"
  setTimeout(donothing(), 5);
  avatar.src = "back2"
  setTimeout(donothing(), 5);
  avatar.src = "back3"
  setTimeout(donothing(), 5);
}

function donothing() {};
<commit_msg>Added stick figure images<commit_after>// Initialize user character
var img = document.querySelector('.imgBox');
var user = document.getElementById('avatar');
var hasMoved = false;

// Initialize information boxes
var intro = document.querySelector('.infoBoxIntro');
var proj = document.querySelector('.infoBoxProj');
var exp = document.querySelector('.infoBoxExp');
var act = document.querySelector('.infoBoxAct');

// Initialize paragraph, mobile mode div and other misc variables
var infoPara = document.getElementById('infoPara');
var mobile = document.querySelector('.mobile');
var down = false;
var key, activeUserForward, activeUserLeft, activeUserRight, activeUserBack, topPos, leftPos, rightPos, botPos, switchBool;

// Get webpage locations of info boxes and user img
var introBounds = intro.getBoundingClientRect();
var projBounds = proj.getBoundingClientRect();
var expBounds = exp.getBoundingClientRect();
var actBounds = act.getBoundingClientRect();
var imgBounds = img.getBoundingClientRect();

// Constructor for box
function Box(x1, x2, y1, y2, type) {
  this.x1 = x1;
  this.x2 = x2;
  this.y1 = y1;
  this.y2 = y2;
  this.type = type;
}
Box.prototype.crossCheck = function() {
  imgBounds = img.getBoundingClientRect();
  if ((imgBounds.top + 10 < this.y1 &&
      imgBounds.top + 10 > this.y2 &&
      imgBounds.left + 10 > this.x1 &&
      imgBounds.left + 10 < this.x2)) {
    window.location = this.type + ".html";
  }
}

// Initialize info boxes
var introBox = new Box(introBounds.left, introBounds.right, introBounds.bottom, introBounds.top, 'intro');
var projBox = new Box(projBounds.left, projBounds.right, projBounds.bottom, projBounds.top, 'proj');
var expBox = new Box(expBounds.left, expBounds.right, expBounds.bottom, expBounds.top, 'exp');
var actBox = new Box(actBounds.left, actBounds.right, actBounds.bottom, actBounds.top, 'act');

// Initialize user character movement functionality: Move
document.addEventListener('keydown', function(e) {
    switchBool = true;
    key = e.keyCode;
    if (!down) {
      switch (key) {
        case 87:
          activeUserForward = setInterval(forward, 40);
          activeImage = setInterval(walkingForward, 40);
          break;
        case 65:
          activeUserLeft = setInterval(left, 40);
          activeImage = setInterval(walkingLeft, 40);
          break;
        case 68:
          activeUserRight = setInterval(right, 40);
          activeImage = setInterval(walkingRight, 40);
          break;
        case 83:
          activeUserBack = setInterval(back, 40);
          activeImage = setInterval(walkingBack, 40);
          break;
        default:
	  switchBool = false;
          break;
      }
      down = true;
    }
	if (switchBool) {
		hasMoved = true;
	}
    introBox.crossCheck();
    projBox.crossCheck();
    expBox.crossCheck();
    actBox.crossCheck();
  }

)

// Initialize user character movement functionality: Stop
document.addEventListener('keyup', function(e) {
  switchBool = true;
  key = e.keyCode;
  switch (key) {
    case 87:
      clearInterval(activeUserForward);
      break;
    case 65:
      clearInterval(activeUserLeft);
      break;
    case 68:
      clearInterval(activeUserRight);
      break;
    case 83:
      clearInterval(activeUserBack);
      break;
    default:
      switchBool = false;
      break;
  }
if (switchBool) {
	clearInterval(activeImage);
        down = false;
	activeImage = setInterval(standingAni, 40);
	}
})

// Relocate user to webpage selected after clicking mobile button
mobile.onclick = function() {
	intro.style.cursor="pointer";
	intro.onclick = function() {
  	window.location = "intro.html";
  }
  proj.style.cursor="pointer";
  proj.onclick = function() {
  	window.location = "proj.html";
  }
  exp.style.cursor="pointer";
  exp.onclick = function() {
  window.location = "exp.html";
  }
  act.style.cursor="pointer";
  act.onclick = function() {
  window.location = "act.html";
  }
  mobile.textContent = "Now tap on any box for more information"
}

// User image movement for animation functions
function forward() {
  topPos = img.offsetTop;
  img.style.top = topPos - 6 + 'px';
}

function left() {
  leftPos = img.offsetLeft;
  img.style.left = leftPos - 6 + 'px'
}

function right() {
  leftPos = img.offsetLeft;
  img.style.left = leftPos + 6 + 'px'
}

function back() {
  topPos = img.offsetTop;
  img.style.top = topPos + 6 + 'px'
}

// Change user image for animations
function walkingLeft() {
  user.src = "stickfigureart/runl1.png";
  setTimeout(donothing(), 5);
  user.src = "stickfigureart/runl2.png";
  setTimeout(donothing(), 5);
  user.src = "stickfigureart/runl3.png";
  setTimeout(donothing(), 5);
}

function walkingRight() {
  user.src = "stickfigureart/runr1.png";
  setTimeout(donothing(), 5);
  user.src = "stickfigureart/runr2.png";
  setTimeout(donothing(), 5);
  user.src = "stickfigureart/runr3.png";
  setTimeout(donothing(), 5);
}

function walkingForward() {
  user.src = "stickfigureart/runf1.png";
  setTimeout(donothing(), 5);
  user.src = "stickfigureart/runf2.png";
  setTimeout(donothing(), 5);
  user.src = "stickfigureart/runf3.png";
  setTimeout(donothing(), 5);
}

function walkingBack() {
  user.src = "stickfigureart/runb1.png";
  setTimeout(donothing(), 5);
  user.src = "stickfigureart/runb2.png";
  setTimeout(donothing(), 5);
  user.src = "stickfigureart/runb3.png";
  setTimeout(donothing(), 5);
}

function donothing() {};

// User image will wave until user moves
function standingAni() {
	if (hasMoved) {
	user.src = "stickfigureart/stand.png";
	}
	else {
	user.src = "stickfigureart/wave1.png";
	setTimeout(donothing(), 5);
  	user.src = "stickfigureart/wave2.png";
  	setTimeout(donothing(), 5);
  	user.src = "stickfigureart/wave3.png";
  	setTimeout(donothing(), 5);
	}
}
<|endoftext|>


<commit_before>require('string.prototype.startswith');

var fs = require('fs');
var bookshelf = require('./src/common/bookshelf_init');
var R = require('ramda');
var Promise = require('bluebird');
var calculateMetrics = require('./src/calculate_metrics');

var changeset = JSON.parse(fs.readFileSync('./test/fixtures/example.json', 'utf8'));
var metrics = calculateMetrics(changeset);
var hashtags = getHashtags(changeset.metadata.comment);

var User = require('./src/models/User');
var Changeset = require('./src/models/Changeset');
var Hashtag = require('./src/models/Hashtag');

function createChangesetIfNotExists (metrics, transaction) {
  return Changeset.where({id: metrics.id}).fetch().then(function (result) {
    if (!result) {
      return Changeset.forge({
        id: metrics.id,
        road_count: metrics.metrics.road_count,
        building_count: metrics.metrics.building_count,
        waterway_count: metrics.metrics.waterway_count,
        poi_count: metrics.metrics.poi_count,
        gpstrace_count: metrics.metrics.gpstrace_count,
        road_km: metrics.metrics.road_km,
        waterway_km: metrics.metrics.waterway_km,
        gpstrace_km: metrics.metrics.gpstrace_km,
        editor: metrics.editor,
        user_id: metrics.user.id,
        created_at: new Date(metrics.created_at)
      }).save(null, {method: 'insert', transacting: transaction});
    } else {
      return result;
    }
  });
}

function createUserIfNotExists (user, transaction) {
  return User.where({id: user.id}).fetch().then(function (result) {
    if (!result) {
      return User.forge({
        id: user.id,
        name: user.name,
        geo_extent: user.geo_extent,
        avatar: user.avatar,
        created_at: new Date()
      }).save(null, {method: 'insert', transacting: transaction});
    } else {
      return result.save({
        geo_extent: user.geo_extent
      });
    }
  });
}

function getHashtags (str) {
  if (!str) return [];
  var wordlist = str.split(' ');
  var hashlist = [];
  wordlist.forEach(function (word) {
    if (word.startsWith('#') && !R.contains(word, hashlist)) {
      word = word.trim();
      word = word.replace(/,\s*$/, '');
      hashlist.push(word.slice(1));
    }
  });
  return hashlist;
}

function createHashtags (hashtags, transaction) {
  return Promise.map(hashtags, function (hashtag) {
    return Hashtag.where('hashtag', hashtag).fetch()
    .then(function (result) {
      if (!result) {
        return Hashtag.forge({
          hashtag: hashtag,
          created_at: new Date()
        }).save(null, {method: 'insert', transacting: transaction});
      } else {
        return result;
      }
    });
  });
}

function addToDB (metrics) {
  return bookshelf.transaction(function (t) {
    return createUserIfNotExists(metrics.user, t)
    .then(function (changeset) {
      return Promise.all([
        createChangesetIfNotExists(metrics, t),
        createHashtags(hashtags, t)
      ])
      .then(function (results) {
        var changeset = results[0];
        var hashtags = results[1];
        return changeset.hashtags().attach(hashtags, {transacting: t});
      })
      .catch(function (err) {
        console.error(err);
      });
    });
  });
}

addToDB(metrics).then(function (results) {
  console.log(results);
});
<commit_msg>Condensed a line in indejx.js<commit_after>require('string.prototype.startswith');

var fs = require('fs');
var bookshelf = require('./src/common/bookshelf_init');
var R = require('ramda');
var Promise = require('bluebird');
var calculateMetrics = require('./src/calculate_metrics');

var changeset = JSON.parse(fs.readFileSync('./test/fixtures/example.json', 'utf8'));
var metrics = calculateMetrics(changeset);
var hashtags = getHashtags(changeset.metadata.comment);

var User = require('./src/models/User');
var Changeset = require('./src/models/Changeset');
var Hashtag = require('./src/models/Hashtag');

function createChangesetIfNotExists (metrics, transaction) {
  return Changeset.where({id: metrics.id}).fetch().then(function (result) {
    if (!result) {
      return Changeset.forge({
        id: metrics.id,
        road_count: metrics.metrics.road_count,
        building_count: metrics.metrics.building_count,
        waterway_count: metrics.metrics.waterway_count,
        poi_count: metrics.metrics.poi_count,
        gpstrace_count: metrics.metrics.gpstrace_count,
        road_km: metrics.metrics.road_km,
        waterway_km: metrics.metrics.waterway_km,
        gpstrace_km: metrics.metrics.gpstrace_km,
        editor: metrics.editor,
        user_id: metrics.user.id,
        created_at: new Date(metrics.created_at)
      }).save(null, {method: 'insert', transacting: transaction});
    } else {
      return result;
    }
  });
}

function createUserIfNotExists (user, transaction) {
  return User.where({id: user.id}).fetch().then(function (result) {
    if (!result) {
      return User.forge({
        id: user.id,
        name: user.name,
        geo_extent: user.geo_extent,
        avatar: user.avatar,
        created_at: new Date()
      }).save(null, {method: 'insert', transacting: transaction});
    } else {
      return result.save({
        geo_extent: user.geo_extent
      });
    }
  });
}

function getHashtags (str) {
  if (!str) return [];
  var wordlist = str.split(' ');
  var hashlist = [];
  wordlist.forEach(function (word) {
    if (word.startsWith('#') && !R.contains(word, hashlist)) {
      word = word.trim();
      word = word.replace(/,\s*$/, '');
      hashlist.push(word.slice(1));
    }
  });
  return hashlist;
}

function createHashtags (hashtags, transaction) {
  return Promise.map(hashtags, function (hashtag) {
    return Hashtag.where('hashtag', hashtag).fetch()
    .then(function (result) {
      if (!result) {
        return Hashtag.forge({
          hashtag: hashtag,
          created_at: new Date()
        }).save(null, {method: 'insert', transacting: transaction});
      } else {
        return result;
      }
    });
  });
}

function addToDB (metrics) {
  return bookshelf.transaction(function (t) {
    return Promise.all([
      createUserIfNotExists(metrics.user, t),
      createChangesetIfNotExists(metrics, t),
      createHashtags(hashtags, t)
    ])
    .then(function (results) {
      var changeset = results[1];
      var hashtags = results[2];
      return changeset.hashtags().attach(hashtags, {transacting: t});
    })
    .catch(function (err) {
      console.error(err);
    });
  });
}

addToDB(metrics).then(function (results) {
  console.log(results);
});
<|endoftext|>


<commit_before>'use strict';
const app = require('app');
const fs = require('fs');
const path = require('path');
const BrowserWindow = require('browser-window');
const Menu = require('menu');
const Tray = require('tray');
const shell = require('shell');
const appMenu = require('./menu');

// report crashes to the Electron project
require('crash-reporter').start();

// adds debug features like hotkeys for triggering dev tools and reload
require('electron-debug')();

const windows = [];

function updateBadge(title) {
	const messageCount = (/\(([0-9]+)\)/).exec(title);
	console.log(messageCount, title);

	if (Tray.displayBalloon) {
		Tray.displayBalloon({
			title: `Title ${messageCount}`,
			content: `Content ${messageCount}`
		});
	}

	if (app.dock) {
		app.dock.setBadge(messageCount ? messageCount[1] : '');
	}
}

function createTeamWindow(team) {
	const win = new BrowserWindow({
		'min-width': 750,
		'min-height': 250,
		'width': 800,
		'height': 600,
		'web-preferences': {
			//'partition': team,
			'plugins': false,

			// fails without this because of CommonJS script detection
			'node-integration': false
		}
	});

	win.loadUrl(`https://${team}.hipchat.com/chat`);
	win.on('page-title-updated', (e, title) => updateBadge(title));
	win.webContents.on('new-window', (e, url) => {
		e.preventDefault();
		shell.openExternal(url);
	});
	win.webContents.on('did-finish-load', () => {
		if (team !== 'www') {
			//win.setTitle(team);
		}
	});

	return win;
}

function openAllTeamWindows(settings) {
	settings.teams.forEach((team, index) => {
		windows.push(createTeamWindow(team, index));
	});
}

app.on('window-all-closed', () => {
	if (process.platform !== 'darwin') {
		app.quit();
	}
});

app.on('ready', () => {
	Menu.setApplicationMenu(appMenu);

	const settingsFile = path.join(app.getPath('userData'), 'settings.json');

	// Default settings
	let settings = {
		teams: ['www']
	};

	fs.access(settingsFile, fs.F_OK, err => {
		// Create settings file if it doesn't exist.
		if (err) {
			fs.writeFileSync(settingsFile, JSON.stringify(settings), 'utf8');
		} else {
			const file = fs.readFileSync(settingsFile, 'utf8');
			settings = JSON.parse(file);
		}

		openAllTeamWindows(settings);
	});
});
<commit_msg>Setting web-security:false should allow pings<commit_after>'use strict';
const app = require('app');
const fs = require('fs');
const path = require('path');
const BrowserWindow = require('browser-window');
const Menu = require('menu');
const Tray = require('tray');
const shell = require('shell');
const appMenu = require('./menu');

// report crashes to the Electron project
require('crash-reporter').start();

// adds debug features like hotkeys for triggering dev tools and reload
require('electron-debug')();

const windows = [];

function updateBadge(title) {
	const messageCount = (/\(([0-9]+)\)/).exec(title);
	console.log(messageCount, title);

	if (Tray.displayBalloon) {
		Tray.displayBalloon({
			title: `Title ${messageCount}`,
			content: `Content ${messageCount}`
		});
	}

	if (app.dock) {
		app.dock.setBadge(messageCount ? messageCount[1] : '');
	}
}

function createTeamWindow(team) {
	const win = new BrowserWindow({
		'min-width': 750,
		'min-height': 250,
		'width': 800,
		'height': 600,
		'web-preferences': {
			'partition': team,
			'plugins': false,

			// fails without this because of CommonJS script detection
			'node-integration': false,

			// required for Hipchat page title updates
			'web-security': false
		}
	});

	win.loadUrl(`https://${team}.hipchat.com/chat`);
	win.on('page-title-updated', (e, title) => {
		//e.preventDefault();
		updateBadge(title);
	});
	win.webContents.on('new-window', (e, url) => {
		e.preventDefault();
		shell.openExternal(url);
	});
	win.webContents.on('did-finish-load', () => {
		if (team !== 'www') {
			win.setTitle(team);
		}
	});

	return win;
}

function openAllTeamWindows(settings) {
	settings.teams.forEach((team, index) => {
		windows.push(createTeamWindow(team, index));
	});
}

app.on('window-all-closed', () => {
	if (process.platform !== 'darwin') {
		app.quit();
	}
});

app.on('ready', () => {
	Menu.setApplicationMenu(appMenu);

	const settingsFile = path.join(app.getPath('userData'), 'settings.json');

	// Default settings
	let settings = {
		teams: ['www']
	};

	fs.access(settingsFile, fs.F_OK, err => {
		// Create settings file if it doesn't exist.
		if (err) {
			fs.writeFileSync(settingsFile, JSON.stringify(settings), 'utf8');
		} else {
			const file = fs.readFileSync(settingsFile, 'utf8');
			settings = JSON.parse(file);
		}

		openAllTeamWindows(settings);
	});
});
<|endoftext|>