<commit_before>export Arwes from './Arwes';
export Words from './Words';
export Frame from './Frame';
export Button from './Button';
export Line from './Line';
export Loading from './Loading';
export Image from './Image';
export Code from './Code';
export Table from './Table';
export Project from './Project';
export Header from './Header';
export Footer from './Footer';
export Grid, { Row, Col } from './Grid';
export Animation from './Animation';
export Appear from './Appear';
export Highlight from './Highlight';
export Puffs from './Puffs';
export Logo from './Logo';

export ThemeProvider from './ThemeProvider';
export withStyles from './tools/withStyles';
export createTheme from './tools/createTheme';

export SoundsProvider from './SoundsProvider';
export withSounds from './tools/withSounds';
export createSounds from './tools/createSounds';

export createLoader from './tools/createLoader';
export createResponsive from './tools/createResponsive';
export createPlayer from './tools/createPlayer';
<commit_msg>[update] package export utils<commit_after>export Arwes from './Arwes';
export Words from './Words';
export Frame from './Frame';
export Button from './Button';
export Line from './Line';
export Loading from './Loading';
export Image from './Image';
export Code from './Code';
export Table from './Table';
export Project from './Project';
export Header from './Header';
export Footer from './Footer';
export Grid, { Row, Col } from './Grid';
export Animation from './Animation';
export Appear from './Appear';
export Highlight from './Highlight';
export Puffs from './Puffs';
export Logo from './Logo';

export ThemeProvider from './ThemeProvider';
export withStyles from './tools/withStyles';
export createTheme from './tools/createTheme';

export SoundsProvider from './SoundsProvider';
export withSounds from './tools/withSounds';
export createSounds from './tools/createSounds';

export createLoader from './tools/createLoader';
export createResponsive from './tools/createResponsive';
export createPlayer from './tools/createPlayer';

import * as toolsUtils from './tools/utils';
export const utils = toolsUtils;
<|endoftext|>


<commit_before>var News = {
  debug: 0,
  newsMinLimit: 1,
  newsMaxLimit: 15,
  unreadMaxCount: 3, // 0-indexed like the list it its counting, actually +1
  msgNewsLimit: 'Nyhetsantall må være et tall mellom '+this.newsMinLimit+' og '+this.newsMaxLimit,
  msgConnectionError: 'Frakoblet fra feeden til ',
  msgUnsupportedFeed: 'Feeden støttes ikke',
  msgCallbackRequired: 'Callback er påkrevd, legg resultatene inn i DOMen',
  msgNoNewsSource: 'Ingen nyhetskilde funnet for valgt tilhørightet',

  // Get is called by background.html periodically, with News.unreadCount as
  // callback. Fetchfeed is also called by popup.html when requested, but
  // without the callback as we already know the amount of unread posts.
  get: function(affiliationObject, limit, callback) {
    if (typeof affiliationObject == 'undefined') {
      if (this.debug) console.log('ERROR:', this.msgUnsupportedFeed);
      return;
    }
    if (!isNumber(limit) || (limit < 1 && 20 < limit)) {
      if (this.debug) console.log('ERROR:', this.msgNewsLimit);
      return;
    }
    if (typeof callback == 'undefined') {
      if (this.debug) console.log('ERROR:', this.msgCallbackRequired);
      return;
    }

    var self = this;
    // Get news the regular way (RSS or Atom feeds)
    if (typeof affiliationObject.feed != 'undefined') {
      Ajaxer.getXml({
        url: affiliationObject.feed,
        success: function(xml) {
          self.parseFeed(xml, affiliationObject, limit, callback);
        },
        error: function(jqXHR, text, err) {
          // Check for XML sent with HTML headers
          if (jqXHR.status == 200 && jqXHR.responseText.match(/^\<\?xml/) != null) {
            xml = jqXHR.responseText;
            self.parseFeed(xml, affiliationObject, limit, callback);
          }
          else {
            if (self.debug) console.log('ERROR:', self.msgConnectionError, affiliationObject.name);
            callback(self.msgConnectionError, affiliationObject.name);
          }
        },
      });
    }
    // Get news the irregular way, through a getNews function defined in the affiliation object
    else if (typeof affiliationObject.getNews != 'undefined') {
      affiliationObject.getNews(limit, function(posts) {
        for (i in posts) {
          posts[i] = self.postProcess(posts[i]);
        }
        callback(posts);
      });
    }
    else {
      console.log('ERROR:', self.msgNoNewsSource);
    }
  },

  // Need to know about the news feeds used in Online Notifier:
  // These RSS fields are always used:
  // - title
  // - link
  // - desc - often wrapped in <![CDATA[content here]]>
  // These RSS fields are sometimes used:
  // - guid - usually the same as link
  // - pubDate
  // - category
  // - dc:creator - author name or username
  // - enclosure - may contain an image as an XML attribute: url="news_post_image.jpg"
  // - source
  // In Atom feeds, these are the usual fields:
  // - id - a useless ID
  // - published - the publishing date, must be parsed
  // - updated - the updated date, must be parsed
  // - title
  // - content - is the full entry as HTML
  // - link[rel="self"] - is this entry in XML format, useless
  // - link[rel="alternate"] - is the entry as text/html, good!
  // - author -> name
  parseFeed: function(xml, affiliationObject, limit, callback) {
    var posts = [];
    var self = this;
    var count = 0;
    // Add each item from RSS feed
    if ($(xml).find('item').length != 0) {
      $(xml).find('item').each( function() {
        if (count++ < limit) {
          var item = self.parseRssItem(this, affiliationObject);
          item = self.postProcess(item);
          posts.push(item);
        }
      });
    }
    // Add each item from Atom feed
    else if ($(xml).find('entry').length != 0) {
      $(xml).find('entry').each( function() {
        if (count++ < limit) {
          var entry = self.parseAtomEntry(this, affiliationObject);
          entry = self.postProcess(entry);
          posts.push(entry);
        }
      });
    }
    else {
      if (this.debug) console.log('ERROR: Unknown feed type, neither RSS nor Atom');
    }
    callback(posts);
  },

  parseRssItem: function(item, affiliationObject) {
    var post = {};

    // - "If I've seen RSS feeds with multiple title fields in one item? Why, yes, yes I have."

    // The popular fields
    post.title = $(item).find("title").filter(':first').text();
    post.link = $(item).find("link").filter(':first').text();
    post.description = $(item).find("description").filter(':first').html();
    if (typeof post.description == 'undefined')
      post.description = $(item).find("description").filter(':first').text();
    // Less used fields
    post.creator = $(item).find("dc:creator").filter(':first').text();
    post.date = $(item).find("pubDate").filter(':first').text().substr(5, 11);
    // Locally stored
    post.image = affiliationObject.placeholder;
    // Tag the posts with the key and name of the feed they came from
    post.feedKey = affiliationObject.key;
    post.feedName = affiliationObject.name;

    // If feed uses CDATA-tags in title and description we need to be more clever
    // to get the information we want outta there (e.g. Adressa)
    var handleCDATA = function(item, field, postField) {
      if (postField.trim() == '' || postField.match('CDATA') != null) {
        var string = $(item).find(field).filter(':first')['0']['innerHTML'];
        if (typeof string != 'undefined') {
          string = string.replace(/(\<|<)?!(\-\-)?\[CDATA\[/g, '');
          string = string.replace(/\]\](\-\-)?(\>|>)?/g, '');
          return string;
        }
      }
      return postField;
    };
    post.title = handleCDATA(item, 'title', post.title);
    post.description = handleCDATA(item, 'description', post.description);

    // If link field is broken by jQuery (dammit moon moon)
    // then check GUID field for a link instead (e.g. Adressa)
    if (post.link.trim() == '') {
      var guid = $(item).find('guid').filter(':first').text();
      if (guid.indexOf('http') != -1) {
        post.link = guid;
      }
    }

    // Check for image in rarely used tags <enclosure> and <bilde>
    try {
      // Universitetsavisa/Adressa does this little trick to get images in their feed
      var enclosure = $(item).find('enclosure').filter(':first');
      if (enclosure.length != 0) {
        post.image = enclosure['0'].attributes.url.textContent;
      }
      // Gemini uses this rather blunt hack to put images in their feed
      var bilde = $(item).find('bilde');
      if (bilde.length != 0) {
        post.image = bilde['0'].textContent;
      }
    }
    catch (err) {
      // Do nothing, we were just checking, move along quitely
    }

    // In case browser does not grok tags with colons, stupid browser
    if (post.creator == '') {
      var tag = ("dc\\:creator").replace( /.*(\:)(.*)/, "$2" );
      $(item).find(tag).each(function(){
        post.creator = $(this).text().trim();
      });
    }
    // Check for author in rarely used <author> field
    if (post.creator == '') {
      // - Universitetsavisa and Adressa uses this
      var author = $(item).find("author").filter(':first').text();
      if (author != '') {
        author = author.trim();
        var pieces = author.match(/[a-zA-Z0-9æøåÆØÅ\.\- ]+/g);
        if (pieces != null) {
          post.creator = pieces[pieces.length-1];
        }
      }
    }

    return post;
  },

  parseAtomEntry: function(entry, affiliationObject) {
    var post = {};

    // The popular fields
    post.title = $(entry).find("title").filter(':first').text();
    post.link = $(entry).find("link[rel='alternate'][type='text/html']").filter(':first').attr('href');
    post.description = $(entry).find("content").filter(':first').text();
    post.creator = $(entry).find("author name").filter(':first').text();
    post.date = $(entry).find("published").filter(':first').text().substr(5, 11);

    // Locally stored
    post.image = affiliationObject.placeholder;
    // Tag the posts with the key and name of the feed they came from
    post.feedKey = affiliationObject.key;
    post.feedName = affiliationObject.name;

    // Parse date field
    post.date = new Date(post.date);
    if (post.date != "Invalid Date")
      post.date = post.date.toDateString();
    else
      post.date = null;

    return post;
  },

  postProcess: function(post) {
    post.image = this.checkDescriptionForImageLink(post.image, post.description);
    post.altLink = this.checkDescriptionForAltLink(post.description);

    // Remove HTML from description (must be done AFTER checking for CDATA tags)
    post.description = post.description.replace(/<[^>]*>/g, ''); // Tags
    // post.description = post.description.replace(/&(#\d+|\w+);/g, ''); // Entities, this works, but ppl should be allowed to use entitites

    // Didn't find a creator, set the feedname as creator
    if (post.creator.length == 0) {
      post.creator = post.feedName;
    }
    // Capitalize creator name either way
    post.creator = post.creator.capitalize();
    // Abbreviate long creator names
    post.creator = this.abbreviateName(post.creator);

    // In case pubDate didn't exist, set to null
    if (post.date == '')
      post.date = null;

    // Trimming
    post.title = post.title.trim();
    post.description = post.description.trim();

    // Shorten 'bedriftspresentasjon' to 'bedpres'
    post.title = post.title.replace(/edrift(s)?presentasjon/gi, 'edpres');
    post.description = post.description.replace(/edrift(s)?presentasjon/gi, 'edpres');

    // Empty title field?
    if (post.title.trim() == '')
      post.title = 'Uten tittel';
    if (post.description.trim() == '')
      post.description = 'Uten tekst';

    return post;
  },

  refreshNewsList: function(items) {
    var freshList = [];
    items.forEach(function(item, index) {
      freshList.push(item.link);
    });
    return JSON.stringify(freshList);
  },

  countNewsAndNotify: function(items, newsList, lastNotifiedName) {
    var unreadCount = 0;
    var maxNewsAmount = this.unreadMaxCount;
    if (items.length-1 < maxNewsAmount)
      maxNewsAmount = items.length-1;

    // Count feed items
    var self = this;
    for (var i=0; i<items.length; i++) {

      var item = items[i];
      var link = item.link;

      // Counting...
      if (newsList.indexOf(link) === -1) {
        unreadCount++;
        // Send a desktop notification about the first new item
        if (unreadCount == 1) {
          if (localStorage[lastNotifiedName] != link) {
            localStorage[lastNotifiedName] = item.link;
            self.showNotification(item);
          }
        }
      }
      // All counted :)
      else {
        if (unreadCount == 0) {
          if (self.debug) console.log('no new posts');
          return 0;
        }
        else if (maxNewsAmount <= unreadCount) {
          if (self.debug) console.log(maxNewsAmount + '+ unread posts');
          return maxNewsAmount + 1;
        }
        else {
          if (self.debug) console.log('1-' + (maxNewsAmount - 1) + ' unread posts');
          return unreadCount;
        }
      }

      // Stop counting if unread number is greater than maxNewsAmount
      if ((maxNewsAmount - 1) < i) { // Remember i is counting 0
        if (self.debug) console.log((maxNewsAmount + 1) + ' unread posts (stopped counting)');
        return maxNewsAmount + 1;
      }
    };

    // We'll usually not end up here
    if (items.length == 0) {
      if (this.debug) console.log('no items to count!');
    }
    else {
      if (this.debug) console.log('ERROR: something went wrong trying to count these items:', items);
    }
    return 0;
  },

  showNotification: function(item) {
    // Demo mode
    if (typeof item == 'undefined') {
      var key = localStorage.affiliationKey1;
      item = {
        title: Affiliation.org[key].name + ' Notifier',
        description: 'Slik ser et nyhetsvarsel ut.\n"Testing.. 1.. 2.. 3.. *BLASTOFF!*"',
        link: Affiliation.org[key].web,
        feedKey: key,
      }
      // Need to run it by the background process because the event listeners are there
      Browser.getBackgroundProcess().Browser.createNotification(item);
    }
    // Normal mode
    else {
      var showIt = function() {
        if (typeof item != 'undefined') {
          if (localStorage.showNotifications == 'true') {

            // Save timestamp
            localStorage.lastNotifiedTime = new Date().getTime();

            // If the organization has an image API, use it
            if (typeof Affiliation.org[item.feedKey].getImage != 'undefined') {
              Affiliation.org[item.feedKey].getImage(item.link, function(link, image) {
                item.image = image[0];
                // Show desktop notification
                Browser.createNotification(item);
              });
            }
            else if (typeof Affiliation.org[item.feedKey].getImages != 'undefined') {
              var links = [];
              links.push(item.link);
              Affiliation.org[item.feedKey].getImages(links, function(links, images) {
                item.image = images[0];
                // Show desktop notification
                Browser.createNotification(item);
              });
            }
            else {
              // Show desktop notification
              Browser.createNotification(item);
            }
          }
        }
        else {
          if (this.debug) console.log('ERROR: notification item was undefined');
        }
      }
      // Make sure notifications are sent with at least 10 seconds inbetween
      if (!DEBUG) {
        var lastTime = localStorage.lastNotifiedTime;
        if (isNumber(lastTime)) {
          var diff = new Date().getTime() - lastTime;
          if (diff < 10000) { // less than 10 seconds?
            setTimeout(showIt, 10000);
          }
          else {
            showIt();
          }
        }
        else {
          showIt();
        }
      }
      else {
        showIt();
      }
    }
  },

  checkDescriptionForImageLink: function(oldImage, description) {
    var regex = new RegExp('src="(http[^"]*(png|jpe?g|bmp))"');
    var pieces = description.match(regex);
    if (pieces != null)
      return pieces[1];
    else
      return oldImage;
  },

  checkDescriptionForAltLink: function(description) {
    // Looking for alternative link, find the first and best full link
    if (typeof description != 'undefined') {
      var altLink = description.match(/href="(http[^"]*)"/);
      if (altLink != null) {
        if (typeof altLink[1] == 'string') {
          return altLink[1];
        }
      }
    }
    else {
      if (this.debug) console.log('ERROR: checking for alternative link in undefined var post.description');
    }
    return null;
  },

  abbreviateName: function(oldName) {
    if (typeof oldName != 'undefined') {
      // Abbreviate middle names if name is long
      if (18 < oldName.length) {
        var pieces = oldName.split(' ');
        if (2 < pieces.length) {
          // Add first name
          var newName = pieces[0];
          // Add one letter per middle name
          for (var i = 1; i < pieces.length - 1; i++)
            newName += ' ' + pieces[i].charAt(0).toUpperCase() + '.';
          // Add last name
          newName += ' ' + pieces[pieces.length-1];
          return newName;
        }
      }
    }
    else {
      if (this.debug) console.log('ERROR: cannot abbreviate an undefined name');
    }
    return oldName;
  },

}
<commit_msg>news: faster and more correct lookup for dc:creator-tags<commit_after>var News = {
  debug: 0,
  newsMinLimit: 1,
  newsMaxLimit: 15,
  unreadMaxCount: 3, // 0-indexed like the list it its counting, actually +1
  msgNewsLimit: 'Nyhetsantall må være et tall mellom '+this.newsMinLimit+' og '+this.newsMaxLimit,
  msgConnectionError: 'Frakoblet fra feeden til ',
  msgUnsupportedFeed: 'Feeden støttes ikke',
  msgCallbackRequired: 'Callback er påkrevd, legg resultatene inn i DOMen',
  msgNoNewsSource: 'Ingen nyhetskilde funnet for valgt tilhørightet',

  // Get is called by background.html periodically, with News.unreadCount as
  // callback. Fetchfeed is also called by popup.html when requested, but
  // without the callback as we already know the amount of unread posts.
  get: function(affiliationObject, limit, callback) {
    if (typeof affiliationObject == 'undefined') {
      if (this.debug) console.log('ERROR:', this.msgUnsupportedFeed);
      return;
    }
    if (!isNumber(limit) || (limit < 1 && 20 < limit)) {
      if (this.debug) console.log('ERROR:', this.msgNewsLimit);
      return;
    }
    if (typeof callback == 'undefined') {
      if (this.debug) console.log('ERROR:', this.msgCallbackRequired);
      return;
    }

    var self = this;
    // Get news the regular way (RSS or Atom feeds)
    if (typeof affiliationObject.feed != 'undefined') {
      Ajaxer.getXml({
        url: affiliationObject.feed,
        success: function(xml) {
          self.parseFeed(xml, affiliationObject, limit, callback);
        },
        error: function(jqXHR, text, err) {
          // Check for XML sent with HTML headers
          if (jqXHR.status == 200 && jqXHR.responseText.match(/^\<\?xml/) != null) {
            xml = jqXHR.responseText;
            self.parseFeed(xml, affiliationObject, limit, callback);
          }
          else {
            if (self.debug) console.log('ERROR:', self.msgConnectionError, affiliationObject.name);
            callback(self.msgConnectionError, affiliationObject.name);
          }
        },
      });
    }
    // Get news the irregular way, through a getNews function defined in the affiliation object
    else if (typeof affiliationObject.getNews != 'undefined') {
      affiliationObject.getNews(limit, function(posts) {
        for (i in posts) {
          posts[i] = self.postProcess(posts[i]);
        }
        callback(posts);
      });
    }
    else {
      console.log('ERROR:', self.msgNoNewsSource);
    }
  },

  // Need to know about the news feeds used in Online Notifier:
  // These RSS fields are always used:
  // - title
  // - link
  // - desc - often wrapped in <![CDATA[content here]]>
  // These RSS fields are sometimes used:
  // - guid - usually the same as link
  // - pubDate
  // - category
  // - dc:creator - author name or username
  // - enclosure - may contain an image as an XML attribute: url="news_post_image.jpg"
  // - source
  // In Atom feeds, these are the usual fields:
  // - id - a useless ID
  // - published - the publishing date, must be parsed
  // - updated - the updated date, must be parsed
  // - title
  // - content - is the full entry as HTML
  // - link[rel="self"] - is this entry in XML format, useless
  // - link[rel="alternate"] - is the entry as text/html, good!
  // - author -> name
  parseFeed: function(xml, affiliationObject, limit, callback) {
    var posts = [];
    var self = this;
    var count = 0;
    // Add each item from RSS feed
    if ($(xml).find('item').length != 0) {
      $(xml).find('item').each( function() {
        if (count++ < limit) {
          var item = self.parseRssItem(this, affiliationObject);
          item = self.postProcess(item);
          posts.push(item);
        }
      });
    }
    // Add each item from Atom feed
    else if ($(xml).find('entry').length != 0) {
      $(xml).find('entry').each( function() {
        if (count++ < limit) {
          var entry = self.parseAtomEntry(this, affiliationObject);
          entry = self.postProcess(entry);
          posts.push(entry);
        }
      });
    }
    else {
      if (this.debug) console.log('ERROR: Unknown feed type, neither RSS nor Atom');
    }
    callback(posts);
  },

  parseRssItem: function(item, affiliationObject) {
    var post = {};

    // - "If I've seen RSS feeds with multiple title fields in one item? Why, yes, yes I have."

    // The popular fields
    post.title = $(item).find("title").filter(':first').text();
    post.link = $(item).find("link").filter(':first').text();
    post.description = $(item).find("description").filter(':first').html();
    if (typeof post.description == 'undefined')
      post.description = $(item).find("description").filter(':first').text();
    // Less used fields
    post.creator = $(item).find("dc\\:creator").filter(':first').text();
    post.date = $(item).find("pubDate").filter(':first').text().substr(5, 11);
    // Locally stored
    post.image = affiliationObject.placeholder;
    // Tag the posts with the key and name of the feed they came from
    post.feedKey = affiliationObject.key;
    post.feedName = affiliationObject.name;

    // If feed uses CDATA-tags in title and description we need to be more clever
    // to get the information we want outta there (e.g. Adressa)
    var handleCDATA = function(item, field, postField) {
      if (postField.trim() == '' || postField.match('CDATA') != null) {
        var string = $(item).find(field).filter(':first')['0']['innerHTML'];
        if (typeof string != 'undefined') {
          string = string.replace(/(\<|<)?!(\-\-)?\[CDATA\[/g, '');
          string = string.replace(/\]\](\-\-)?(\>|>)?/g, '');
          return string;
        }
      }
      return postField;
    };
    post.title = handleCDATA(item, 'title', post.title);
    post.description = handleCDATA(item, 'description', post.description);

    // If link field is broken by jQuery (dammit moon moon)
    // then check GUID field for a link instead (e.g. Adressa)
    if (post.link.trim() == '') {
      var guid = $(item).find('guid').filter(':first').text();
      if (guid.indexOf('http') != -1) {
        post.link = guid;
      }
    }

    // Check for image in rarely used tags <enclosure> and <bilde>
    try {
      // Universitetsavisa/Adressa does this little trick to get images in their feed
      var enclosure = $(item).find('enclosure').filter(':first');
      if (enclosure.length != 0) {
        post.image = enclosure['0'].attributes.url.textContent;
      }
      // Gemini uses this rather blunt hack to put images in their feed
      var bilde = $(item).find('bilde');
      if (bilde.length != 0) {
        post.image = bilde['0'].textContent;
      }
    }
    catch (err) {
      // Do nothing, we were just checking, move along quitely
    }

    // Check for author in rarely used <author> field
    if (post.creator == '') {
      // - Universitetsavisa and Adressa uses this
      var author = $(item).find("author").filter(':first').text();
      if (author != '') {
        author = author.trim();
        var pieces = author.match(/[a-zA-Z0-9æøåÆØÅ\.\- ]+/g);
        if (pieces != null) {
          post.creator = pieces[pieces.length-1];
        }
      }
    }

    return post;
  },

  parseAtomEntry: function(entry, affiliationObject) {
    var post = {};

    // The popular fields
    post.title = $(entry).find("title").filter(':first').text();
    post.link = $(entry).find("link[rel='alternate'][type='text/html']").filter(':first').attr('href');
    post.description = $(entry).find("content").filter(':first').text();
    post.creator = $(entry).find("author name").filter(':first').text();
    post.date = $(entry).find("published").filter(':first').text().substr(5, 11);

    // Locally stored
    post.image = affiliationObject.placeholder;
    // Tag the posts with the key and name of the feed they came from
    post.feedKey = affiliationObject.key;
    post.feedName = affiliationObject.name;

    // Parse date field
    post.date = new Date(post.date);
    if (post.date != "Invalid Date")
      post.date = post.date.toDateString();
    else
      post.date = null;

    return post;
  },

  postProcess: function(post) {
    post.image = this.checkDescriptionForImageLink(post.image, post.description);
    post.altLink = this.checkDescriptionForAltLink(post.description);

    // Remove HTML from description (must be done AFTER checking for CDATA tags)
    post.description = post.description.replace(/<[^>]*>/g, ''); // Tags
    // post.description = post.description.replace(/&(#\d+|\w+);/g, ''); // Entities, this works, but ppl should be allowed to use entitites

    // Didn't find a creator, set the feedname as creator
    if (post.creator.length == 0) {
      post.creator = post.feedName;
    }
    // Capitalize creator name either way
    post.creator = post.creator.capitalize();
    // Abbreviate long creator names
    post.creator = this.abbreviateName(post.creator);

    // In case pubDate didn't exist, set to null
    if (post.date == '')
      post.date = null;

    // Trimming
    post.title = post.title.trim();
    post.description = post.description.trim();

    // Shorten 'bedriftspresentasjon' to 'bedpres'
    post.title = post.title.replace(/edrift(s)?presentasjon/gi, 'edpres');
    post.description = post.description.replace(/edrift(s)?presentasjon/gi, 'edpres');

    // Empty title field?
    if (post.title.trim() == '')
      post.title = 'Uten tittel';
    if (post.description.trim() == '')
      post.description = 'Uten tekst';

    return post;
  },

  refreshNewsList: function(items) {
    var freshList = [];
    items.forEach(function(item, index) {
      freshList.push(item.link);
    });
    return JSON.stringify(freshList);
  },

  countNewsAndNotify: function(items, newsList, lastNotifiedName) {
    var unreadCount = 0;
    var maxNewsAmount = this.unreadMaxCount;
    if (items.length-1 < maxNewsAmount)
      maxNewsAmount = items.length-1;

    // Count feed items
    var self = this;
    for (var i=0; i<items.length; i++) {

      var item = items[i];
      var link = item.link;

      // Counting...
      if (newsList.indexOf(link) === -1) {
        unreadCount++;
        // Send a desktop notification about the first new item
        if (unreadCount == 1) {
          if (localStorage[lastNotifiedName] != link) {
            localStorage[lastNotifiedName] = item.link;
            self.showNotification(item);
          }
        }
      }
      // All counted :)
      else {
        if (unreadCount == 0) {
          if (self.debug) console.log('no new posts');
          return 0;
        }
        else if (maxNewsAmount <= unreadCount) {
          if (self.debug) console.log(maxNewsAmount + '+ unread posts');
          return maxNewsAmount + 1;
        }
        else {
          if (self.debug) console.log('1-' + (maxNewsAmount - 1) + ' unread posts');
          return unreadCount;
        }
      }

      // Stop counting if unread number is greater than maxNewsAmount
      if ((maxNewsAmount - 1) < i) { // Remember i is counting 0
        if (self.debug) console.log((maxNewsAmount + 1) + ' unread posts (stopped counting)');
        return maxNewsAmount + 1;
      }
    };

    // We'll usually not end up here
    if (items.length == 0) {
      if (this.debug) console.log('no items to count!');
    }
    else {
      if (this.debug) console.log('ERROR: something went wrong trying to count these items:', items);
    }
    return 0;
  },

  showNotification: function(item) {
    // Demo mode
    if (typeof item == 'undefined') {
      var key = localStorage.affiliationKey1;
      item = {
        title: Affiliation.org[key].name + ' Notifier',
        description: 'Slik ser et nyhetsvarsel ut.\n"Testing.. 1.. 2.. 3.. *BLASTOFF!*"',
        link: Affiliation.org[key].web,
        feedKey: key,
      }
      // Need to run it by the background process because the event listeners are there
      Browser.getBackgroundProcess().Browser.createNotification(item);
    }
    // Normal mode
    else {
      var showIt = function() {
        if (typeof item != 'undefined') {
          if (localStorage.showNotifications == 'true') {

            // Save timestamp
            localStorage.lastNotifiedTime = new Date().getTime();

            // If the organization has an image API, use it
            if (typeof Affiliation.org[item.feedKey].getImage != 'undefined') {
              Affiliation.org[item.feedKey].getImage(item.link, function(link, image) {
                item.image = image[0];
                // Show desktop notification
                Browser.createNotification(item);
              });
            }
            else if (typeof Affiliation.org[item.feedKey].getImages != 'undefined') {
              var links = [];
              links.push(item.link);
              Affiliation.org[item.feedKey].getImages(links, function(links, images) {
                item.image = images[0];
                // Show desktop notification
                Browser.createNotification(item);
              });
            }
            else {
              // Show desktop notification
              Browser.createNotification(item);
            }
          }
        }
        else {
          if (this.debug) console.log('ERROR: notification item was undefined');
        }
      }
      // Make sure notifications are sent with at least 10 seconds inbetween
      if (!DEBUG) {
        var lastTime = localStorage.lastNotifiedTime;
        if (isNumber(lastTime)) {
          var diff = new Date().getTime() - lastTime;
          if (diff < 10000) { // less than 10 seconds?
            setTimeout(showIt, 10000);
          }
          else {
            showIt();
          }
        }
        else {
          showIt();
        }
      }
      else {
        showIt();
      }
    }
  },

  checkDescriptionForImageLink: function(oldImage, description) {
    var regex = new RegExp('src="(http[^"]*(png|jpe?g|bmp))"');
    var pieces = description.match(regex);
    if (pieces != null)
      return pieces[1];
    else
      return oldImage;
  },

  checkDescriptionForAltLink: function(description) {
    // Looking for alternative link, find the first and best full link
    if (typeof description != 'undefined') {
      var altLink = description.match(/href="(http[^"]*)"/);
      if (altLink != null) {
        if (typeof altLink[1] == 'string') {
          return altLink[1];
        }
      }
    }
    else {
      if (this.debug) console.log('ERROR: checking for alternative link in undefined var post.description');
    }
    return null;
  },

  abbreviateName: function(oldName) {
    if (typeof oldName != 'undefined') {
      // Abbreviate middle names if name is long
      if (18 < oldName.length) {
        var pieces = oldName.split(' ');
        if (2 < pieces.length) {
          // Add first name
          var newName = pieces[0];
          // Add one letter per middle name
          for (var i = 1; i < pieces.length - 1; i++)
            newName += ' ' + pieces[i].charAt(0).toUpperCase() + '.';
          // Add last name
          newName += ' ' + pieces[pieces.length-1];
          return newName;
        }
      }
    }
    else {
      if (this.debug) console.log('ERROR: cannot abbreviate an undefined name');
    }
    return oldName;
  },

}
<|endoftext|>


<commit_before>var UTMZCookie = require('./utmz-cookie.js');
var url = require('url');

function BringBackTheUTMZCookie(tracker, config) {
    this.tracker = tracker;
    this.debug = config.debug || false;
    this.debugMessage('loaded');
    if(config.cookies) {
        this.existingCookie = this.loadExistingCookie(config.cookies);
    }
}

BringBackTheUTMZCookie.prototype.runDefaultBehaviour = function() {
    // Is this a new domain?
    var currentUrl = document.location.toString()
    if (this.isSameDomain(document.referrer, currentUrl)) {
        return false;
    }

    if (this.runTaggedCampaignBehaviour()) { return true; }
    else if (this.runReferralBehaviour()) { return true; }
    else if (this.existingCookie && this.existingCookie.isLoaded) { return false; }
    else { this.runDirectBehaviour() }
};

BringBackTheUTMZCookie.prototype.loadExistingCookie = function (cookies) {
    // Is there any existing cookie? If so, get it.
    var existingCookie = new UTMZCookie({});
    if (cookies.indexOf("__utmz=") > -1) {
        var cookieString = cookies.match(/__utmz=[^;]*;/)[0];
        existingCookie.loadFromCookieString(cookieString.substring(7, cookieString.length-1));
        this.debugMessage(cookieString);
    }
    return existingCookie;
};

BringBackTheUTMZCookie.prototype.runDirectBehaviour = function() {
    var directCookie = new UTMZCookie({});
    directCookie.loadAsDirect();
    this.setOrUpdate(directCookie);
};

BringBackTheUTMZCookie.prototype.runReferralBehaviour = function() {
    // Is there a referer string?
    if (!document.referrer) {
        return false;
    }

    // Is it a parseable and known referer? If so, set it correctly.
    //TODO: parse special referrers correctly.

    // Otherwise, set as a referal
    var referrerCookie = new UTMZCookie({});
    referrerCookie.loadFromReferrerString(document.referrer);
    this.setOrUpdate(referrerCookie);
};

BringBackTheUTMZCookie.prototype.setOrUpdate = function(newCookie) {
    if (this.existingCookie && this.existingCookie.isLoaded && newCookie.nooverride) {
        this.existingCookie.expirationDate = '';
        document.cookie = this.existingCookie.cookieString();
        return true;
    // If existing cookie, and same as URL string, increment campaign and refresh
    } else if (this.existingCookie && this.existingCookie.isLoaded && this.existingCookie.equivalent(newCookie)) {
        this.existingCookie.campaignNumber++;
        this.existingCookie.expirationDate = '';
        document.cookie = this.existingCookie.cookieString();
        return true;
    } else {
        // Else, set url string as cookie
        document.cookie = newCookie.cookieString();
        return true;
    }
};

BringBackTheUTMZCookie.prototype.runTaggedCampaignBehaviour = function() {
    // Are there campaign tags in the URL?
    var currentUrl = document.location.toString()
    if (!this.hasCampaignTags(currentUrl)) {
        return false;
    }
    // Load from the URL String
    var urlCookie = new UTMZCookie({});
    urlCookie.loadFromURLString(currentUrl);
    // If existing cookie, and URL string is nooverride, refresh expiry of existing
    return this.setOrUpdate(urlCookie);
};

BringBackTheUTMZCookie.prototype.isSameDomain = function (url1, url2) {
    if (url1 && url2) {
        var domain1 = url.parse(url1).hostname.replace(/^www\./,'');
        var domain2 = url.parse(url2).hostname.replace(/^www\./,'');
        return (domain1 == domain2);
    } else { return false; }
};

BringBackTheUTMZCookie.prototype.hasCampaignTags = function (currentUrl) {
        var query = url.parse(currentUrl).query;
        if (query == undefined) return false;
        var medium = query.indexOf('utm_medium=') > -1;
        var source = query.indexOf('utm_source=') > -1;
        var campaign = query.indexOf('utm_campaign=') > -1;
        return (medium && source && campaign);
};
// Enables / disables debug output.
BringBackTheUTMZCookie.prototype.setDebug = function(enabled) {
    this.debug = enabled;
};

/**
 * Displays a debug message in the console, if debugging is enabled.
 */
BringBackTheUTMZCookie.prototype.debugMessage = function(message) {
    if (!this.debug) return;
    if (console) console.debug('utmz: ' + message);
};

module.exports = BringBackTheUTMZCookie;
<commit_msg>fixed a small cookie parsing error<commit_after>var UTMZCookie = require('./utmz-cookie.js');
var url = require('url');

function BringBackTheUTMZCookie(tracker, config) {
    this.tracker = tracker;
    this.debug = config.debug || false;
    this.debugMessage('loaded');
    if(config.cookies) {
        this.existingCookie = this.loadExistingCookie(config.cookies);
    }
}

BringBackTheUTMZCookie.prototype.runDefaultBehaviour = function() {
    // Is this a new domain?
    var currentUrl = document.location.toString()
    if (this.isSameDomain(document.referrer, currentUrl)) {
        return true;
    }

    if (this.runTaggedCampaignBehaviour()) { return true; }
    else if (this.runReferralBehaviour()) { return true; }
    else if (this.existingCookie && this.existingCookie.isLoaded) { return true; }
    else {
        this.runDirectBehaviour();
        return true;
     }
};

BringBackTheUTMZCookie.prototype.loadExistingCookie = function (cookies) {
    // Is there any existing cookie? If so, get it.
    var existingCookie = new UTMZCookie({});
    if (cookies.indexOf("__utmz=") > -1) {
    var cookieString = cookies.match(/__utmz=[^;]*/)[0];
        existingCookie.loadFromCookieString(cookieString.substring(7, cookieString.length));
        this.debugMessage(cookieString);
    }
    return existingCookie;
};

BringBackTheUTMZCookie.prototype.runDirectBehaviour = function() {
    var directCookie = new UTMZCookie({});
    directCookie.loadAsDirect();
    this.setOrUpdate(directCookie);
};

BringBackTheUTMZCookie.prototype.runReferralBehaviour = function() {
    // Is there a referer string?
    if (!document.referrer) {
        return false;
    }

    // Is it a parseable and known referer? If so, set it correctly.
    //TODO: parse special referrers correctly.

    // Otherwise, set as a referal
    var referrerCookie = new UTMZCookie({});
    referrerCookie.loadFromReferrerString(document.referrer);
    this.setOrUpdate(referrerCookie);
};

BringBackTheUTMZCookie.prototype.setOrUpdate = function(newCookie) {
    if (this.existingCookie && this.existingCookie.isLoaded && newCookie.nooverride) {
        this.existingCookie.expirationDate = '';
        document.cookie = this.existingCookie.cookieString();
        return true;
    // If existing cookie, and same as URL string, increment campaign and refresh
    } else if (this.existingCookie && this.existingCookie.isLoaded && this.existingCookie.equivalent(newCookie)) {
        this.existingCookie.campaignNumber++;
        this.existingCookie.expirationDate = '';
        document.cookie = this.existingCookie.cookieString();
        return true;
    } else {
        // Else, set url string as cookie
        document.cookie = newCookie.cookieString();
        return true;
    }
};

BringBackTheUTMZCookie.prototype.runTaggedCampaignBehaviour = function() {
    // Are there campaign tags in the URL?
    var currentUrl = document.location.toString()
    if (!this.hasCampaignTags(currentUrl)) {
        return false;
    }
    // Load from the URL String
    var urlCookie = new UTMZCookie({});
    urlCookie.loadFromURLString(currentUrl);
    // If existing cookie, and URL string is nooverride, refresh expiry of existing
    return this.setOrUpdate(urlCookie);
};

BringBackTheUTMZCookie.prototype.isSameDomain = function (url1, url2) {
    if (url1 && url2) {
        var domain1 = url.parse(url1).hostname.replace(/^www\./,'');
        var domain2 = url.parse(url2).hostname.replace(/^www\./,'');
        return (domain1 == domain2);
    } else { return false; }
};

BringBackTheUTMZCookie.prototype.hasCampaignTags = function (currentUrl) {
        var query = url.parse(currentUrl).query;
        if (query == undefined) return false;
        var medium = query.indexOf('utm_medium=') > -1;
        var source = query.indexOf('utm_source=') > -1;
        var campaign = query.indexOf('utm_campaign=') > -1;
        return (medium && source && campaign);
};
// Enables / disables debug output.
BringBackTheUTMZCookie.prototype.setDebug = function(enabled) {
    this.debug = enabled;
};

/**
 * Displays a debug message in the console, if debugging is enabled.
 */
BringBackTheUTMZCookie.prototype.debugMessage = function(message) {
    if (!this.debug) return;
    if (console) console.debug('utmz: ' + message);
};

module.exports = BringBackTheUTMZCookie;
<|endoftext|>


<commit_before>'use strict';

(function () {

  function setupRequestAnimationFrame () {
    var lastTime = 0;
    var vendors = ['webkit', 'moz', 'ms', 'o'];

    for (var i = 0; !window.requestAnimationFrame && i < vendors.length; i += 1) {
      var vendor = vendors[i];
      window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame'];
      window.cancelAnimationFrame = window[vendor + 'CancelAnimationFrame'] ||
        window[vendor + 'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame) {
      window.requestAnimationFrame = function (callback) {
        var now = new Date().getTime();
        var nextTime = Math.max(0, 16 - (now - lastTime));

        var id = window.setTimeout(function () {
          callback(now + nextTime);
        }, nextTime);

        lastTime = now + nextTime;
        return id;
      };
    }

    if (!window.cancelAnimationFrame) {
      window.cancelAnimationFrame = function (id) {
        clearTimeout(id);
      };
    }
  }

  setupRequestAnimationFrame();

  function getSlik (Immutable) {

    function Linear (fromValue, toValue, progress) {

    }

    function Ease (fromValue, toValue, progress) {

    }

    function EaseIn (fromValue, toValue, progress) {

    }

    function EaseOut (fromValue, toValue, progress) {

    }

    function Animation (initial) {
      var self = this;

      var fromValues = Immutable.fromJS(initial);
      var toValues = Immutable.Map();
      var frameRate = 1000 / 30;
      var easing = Linear;
      var shouldLoop = false;

      var events = {
        all: [],
        start: [],
        stop: [],
        pause: [],
        end: [],
        update: [],
        loop: []
      };

      // Set the frameRate
      function fps (input) {
        frameRate = 1000 / input;
        return self;
      }

      // Set from values
      function from (input) {
        fromValues = Immutable.fromJS(input);
        return self;
      }

      // Set to values
      function to (input) {
        toValues = Immutable.fromJS(input);
        return self;
      }

      // Set easing
      function ease (input) {
        easing = input;
        return self;
      }

      // Set whether the animation should loop
      function loop (input) {
        shouldLoop = input;
        return self;
      }

      // Invert animation values
      function invert () {
        return self;
      }

      // Play animation in reverse
      function reverse () {
        var temp = fromValues;
        fromValues = toValues;
        toValues = temp;
        return self;
      }

      // Start or resume animation
      function start () {
        return self;
      }

      // Stop animation and resume from beginning
      function stop () {
        return self;
      }

      // Pause animation and resume from this point
      function pause () {
        return self;
      }

      // Manually start the animation / step forward in time
      function update () {
        return self;
      }

      // Add event listener
      function bind (type, callback) {
        if (!(type in events)) {
          throw new Error('Unknown event: ' + type);
        }

        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        var index = events[type].indexOf(callback);

        if (index < 0) {
          events[type].push(callback);
        }

        return self;
      }

      // Remove event listener
      function unbind (type, callback) {
        if (!(type in events)) {
          throw new Error('Unknown event: ' + type);
        }

        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        var index = events[type].indexOf(callback);

        if (index >= 0) {
          events[type].splice(index, 1);
        }

        return self;
      }

      // Run on complete and automatically unbind
      function then (callback) {
        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        function thenCallback () {
          callback();
          unbind('end', thenCallback);
        }

        bind('end', thenCallback);

        return self;
      }

      self.fps = self.frameRate = fps;
      self.from = from;
      self.to = to;
      self.ease = ease;
      self.loop = loop;
      self.invert = invert;
      self.reverse = reverse;
      self.start = start;
      self.stop = self.reset = stop;
      self.pause = pause;
      self.update = update;
      self.bind = self.on = bind;
      self.unbind = self.off = unbind;
      self.then = then;
    }

    return {
      Animation: Animation,
      Easing: {
        Linear: Linear,
        Ease: Ease,
        EaseIn: EaseIn,
        EaseOut: EaseOut
      }
    };

  }

  // Export for commonjs / browserify
  if (typeof exports === 'object' && typeof module !== 'undefined') {
    module.exports = getSlik(require('immutable')); // eslint-disable-line no-undef
  // Export for amd / require
  } else if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef
    define(['immutable'], function (Immutable) { // eslint-disable-line no-undef
      return getSlik(Immutable);
    });
  // Export globally
  } else {
    var root;

    if (typeof window !== 'undefined') {
      root = window;
    } else if (typeof global !== 'undefined') {
      root = global; // eslint-disable-line no-undef
    } else if (typeof self !== 'undefined') {
      root = self; // eslint-disable-line no-undef
    } else {
      root = this;
    }

    root.Slik = getSlik(root.Immutable);
  }

})();
<commit_msg>Setup easing function multipliers<commit_after>'use strict';

(function () {

  function setupRequestAnimationFrame () {
    var lastTime = 0;
    var vendors = ['webkit', 'moz', 'ms', 'o'];

    for (var i = 0; !window.requestAnimationFrame && i < vendors.length; i += 1) {
      var vendor = vendors[i];
      window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame'];
      window.cancelAnimationFrame = window[vendor + 'CancelAnimationFrame'] ||
        window[vendor + 'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame) {
      window.requestAnimationFrame = function (callback) {
        var now = new Date().getTime();
        var nextTime = Math.max(0, 16 - (now - lastTime));

        var id = window.setTimeout(function () {
          callback(now + nextTime);
        }, nextTime);

        lastTime = now + nextTime;
        return id;
      };
    }

    if (!window.cancelAnimationFrame) {
      window.cancelAnimationFrame = function (id) {
        clearTimeout(id);
      };
    }
  }

  setupRequestAnimationFrame();

  function getSlik (Immutable) {

    function multiply (fromValue, toValue, multiplier) {
      multiplier = Math.min(Math.max(multiplier, 0), 1);
      return fromValue * multiplier + toValue * (1 - multiplier);
    }

    function Linear (fromValue, toValue, progress) {
      var multiplier = progress;
      return multiply(fromValue, toValue, multiplier);
    }

    function Ease (fromValue, toValue, progress) {
      var multiplier = (1 - Math.cos(progress * Math.PI)) / 2;
      return multiply(fromValue, toValue, multiplier);
    }

    function EaseIn (fromValue, toValue, progress) {
      var multiplier = (1 - Math.cos(progress * Math.PI / 2));
      return multiply(fromValue, toValue, multiplier);
    }

    function EaseOut (fromValue, toValue, progress) {
      var multiplier = Math.cos((1 - progress) * Math.PI / 2);
      return multiply(fromValue, toValue, multiplier);
    }

    function Animation (initial) {
      var self = this;

      var fromValues = Immutable.fromJS(initial);
      var toValues = Immutable.Map();
      var frameRate = 1000 / 30;
      var easing = Linear;
      var shouldLoop = false;

      var events = {
        all: [],
        start: [],
        stop: [],
        pause: [],
        end: [],
        update: [],
        loop: []
      };

      // Set the frameRate
      function fps (input) {
        frameRate = 1000 / input;
        return self;
      }

      // Set from values
      function from (input) {
        fromValues = Immutable.fromJS(input);
        return self;
      }

      // Set to values
      function to (input) {
        toValues = Immutable.fromJS(input);
        return self;
      }

      // Set easing
      function ease (input) {
        easing = input;
        return self;
      }

      // Set whether the animation should loop
      function loop (input) {
        shouldLoop = input;
        return self;
      }

      // Invert animation values
      function invert () {
        return self;
      }

      // Play animation in reverse
      function reverse () {
        var temp = fromValues;
        fromValues = toValues;
        toValues = temp;
        return self;
      }

      // Start or resume animation
      function start () {
        return self;
      }

      // Stop animation and resume from beginning
      function stop () {
        return self;
      }

      // Pause animation and resume from this point
      function pause () {
        return self;
      }

      // Manually start the animation / step forward in time
      function update () {
        return self;
      }

      // Add event listener
      function bind (type, callback) {
        if (!(type in events)) {
          throw new Error('Unknown event: ' + type);
        }

        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        var index = events[type].indexOf(callback);

        if (index < 0) {
          events[type].push(callback);
        }

        return self;
      }

      // Remove event listener
      function unbind (type, callback) {
        if (!(type in events)) {
          throw new Error('Unknown event: ' + type);
        }

        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        var index = events[type].indexOf(callback);

        if (index >= 0) {
          events[type].splice(index, 1);
        }

        return self;
      }

      // Run on complete and automatically unbind
      function then (callback) {
        if (typeof callback !== 'function') {
          throw new Error('Callback must be a function, instead got: ' + (typeof callback));
        }

        function thenCallback () {
          callback();
          unbind('end', thenCallback);
        }

        bind('end', thenCallback);

        return self;
      }

      self.fps = self.frameRate = fps;
      self.from = from;
      self.to = to;
      self.ease = ease;
      self.loop = loop;
      self.invert = invert;
      self.reverse = reverse;
      self.start = start;
      self.stop = self.reset = stop;
      self.pause = pause;
      self.update = update;
      self.bind = self.on = bind;
      self.unbind = self.off = unbind;
      self.then = then;
    }

    return {
      Animation: Animation,
      Easing: {
        Linear: Linear,
        Ease: Ease,
        EaseIn: EaseIn,
        EaseOut: EaseOut
      }
    };

  }

  // Export for commonjs / browserify
  if (typeof exports === 'object' && typeof module !== 'undefined') {
    module.exports = getSlik(require('immutable')); // eslint-disable-line no-undef
  // Export for amd / require
  } else if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef
    define(['immutable'], function (Immutable) { // eslint-disable-line no-undef
      return getSlik(Immutable);
    });
  // Export globally
  } else {
    var root;

    if (typeof window !== 'undefined') {
      root = window;
    } else if (typeof global !== 'undefined') {
      root = global; // eslint-disable-line no-undef
    } else if (typeof self !== 'undefined') {
      root = self; // eslint-disable-line no-undef
    } else {
      root = this;
    }

    root.Slik = getSlik(root.Immutable);
  }

})();
<|endoftext|>


<commit_before>import React, { Component } from 'react';

export default function ClickOutsideHOC(WrappedComponent) {
    function getDisplayName() {
        return WrappedComponent.displayName || WrappedComponent.name || 'Component';
    }

    return class ClickOutside extends Component {
        static displayName = `withClickOutsideHOC(${getDisplayName()})`;

        render() {
            return (
                <WrappedComponent {...this.props} />
            );
        }
    };
}
<commit_msg>Implement functionality to register if a click occured outside of the decorated component<commit_after>import React, { Component, PropTypes } from 'react';

export default function ClickOutsideHOC(WrappedComponent) {
    const EVENT = 'mousedown';

    function getDisplayName() {
        return WrappedComponent.displayName || WrappedComponent.name || 'Component';
    }

    return class ClickOutside extends Component {
        static displayName = `withClickOutsideHOC(${getDisplayName()})`;

        static propTypes = {
            onClickOutside: PropTypes.func.isRequired,
        }

        componentDidMount() {
            global.addEventListener(EVENT, this.onClickOutside);
        }

        componentWillUnmount() {
            global.removeEventListener(EVENT, this.onClickOutside);
        }

        hasClickedOutside(containerNode, targetNode) {
            return !containerNode.contains(targetNode);
        }

        onClickOutside = ({ target }) => {
            const { containerNode, hasClickedOutside } = this;

            if (containerNode && hasClickedOutside(containerNode, target)) {
                this.props.onClickOutside();
            }
        }

        containerRef = (node) => {
            this.containerNode = node;
        }

        render() {
            const { onClickOutside, ...rest } = this.props;

            return (
                <div ref={this.containerRef}>
                    <WrappedComponent {...rest} />
                </div>
            );
        }
    };
}
<|endoftext|>


<commit_before>import url from 'url';
import { CancelToken } from 'axios';
import isAbsoluteUrl from 'axios/lib/helpers/isAbsoluteURL';
import combineURLs from 'axios/lib/helpers/combineURLs';
import getTargetAxios from './getTargetAxios';
import filterResponseHeaders from './filterResponseHeaders';

const pushableMethods = ['GET']; // TODO can I push_promise HEAD?

function canPush(pageResponse, requestURL, config) {
  return pageResponse.stream && pageResponse.stream.pushAllowed &&
    pushableMethods.includes(config.method.toUpperCase());
  // TODO also check domain
  // TODO don't push the same thing multiple times
}

// TODO should also give the option to disable all push requests and
//    instead just make requests as normal (for pre-rendering the html)
//    or ignore all requests (for after the response has been sent).

// for request that contain no data (GET, HEAD, DELETE)
function getRequestConfigWithoutData(method, [arg1, arg2]) {
  if (typeof arg1 === 'string') {
    const config = { ...arg2 };
    config.url = arg1;
    config.method = method || config.method || 'GET';
    return config;
  } else {
    return arg1;
  }
}
// for requests that contain data (POST, PUT)
function getRequestConfigWithData(method, [arg1, arg2, arg3]) {
  if (typeof arg1 === 'string') {
    const config = { ...arg3 };
    config.url = arg1;
    config.method = method || config.method || 'POST';
    return config;
  } else {
    return arg2 || arg1;
  }
}

function getWord(str) {
  const result = str && /\w+/.exec(str);
  return result && result[0];
}

/**
 * Wraps axios in something that will monitor requests/responses and
 * will issue push promises
 * @param {pageResponse} http2.Http2ServerResponse
 * @param {axiosParam} (optional) instance of axios, or axios config object
 * @return {object} returns an axios instance that will issue push promises automatically.
 */
export default function prepareAxios(pageResponse, axiosParam = null) {
  const targetAxios = getTargetAxios(axiosParam);

  if (global.window || !pageResponse) {
    // don't wrap it if on client side
    return targetAxios;
  }

  // Unfortunately, we can't use a real request interceptor.
  // Axios doesn't call its request interceptors immediately, so the
  // page response stream could close before we have a chance to send
  // the push promise.
  // This is why we have to wrap axios instead of just adding interceptors.

  function interceptRequest(params, method, hasData) {
    const config = hasData ?
      getRequestConfigWithData(method, params) :
      getRequestConfigWithoutData(method, params);

    const baseURL = targetAxios.defaults.baseURL || config.baseURL;
    const requestURLString = baseURL && !isAbsoluteUrl(config.url) ?
      combineURLs(baseURL, config.url) :
      config.url;
    const requestURL = url.parse(requestURLString);

    if (canPush(pageResponse, requestURL, config)) {
      // issue a push promise, with correct authority, path, and headers.
      // http/2 pseudo headers: :method, :path, :scheme, :authority
      const requestHeaders = {
        ...config.headers,
        ':path': requestURL.path,
        ':authority': requestURL.host,
        ':method': config.method.toUpperCase(),
        ':scheme': getWord(requestURL.protocol) || 'https'
      };

      const cancelSource = CancelToken.source();

      const pushResponsePromise = new Promise((resolve) => {
        pageResponse.createPushResponse(
          requestHeaders,
          (err, pushResponse) => {
            if (err) {
              // Can't reject the promise because nothing will catch() it.
              cancelSource.cancel('Push promise failed');
            } else {
              pushResponse.on('close', () => {
                cancelSource.cancel('Push stream closed');
              });
              resolve(pushResponse);
            }
          }
        );
      });

      const newConfig = {
        ...config,
        responseType: 'stream',
        cancelToken: cancelSource.token,
        pushResponsePromise
      };

      targetAxios.request(newConfig).catch(() => {});
      // TODO should the resulting promise be returned?
      //  Currently, I can't, because data is always a stream,
      //  but returning it could be useful for pushing any follow-up api calls.
    }
    // return an empty promise that never resolves.
    const emptyPromise = new Promise(() => {});
    emptyPromise.empty = true;
    return emptyPromise;
  }

  if (!targetAxios.usingIsomorphicPushInterceptors) {
    targetAxios.interceptors.response.use(
      responseInterceptor,
      responseRejectedInterceptor
    );
    targetAxios.usingIsomorphicPushInterceptors = true;
  }

  function axiosWrapper(...params) {
    return interceptRequest(params, null, false);
  }

  axiosWrapper.request = (...params) =>
    interceptRequest(params, null, false);
  axiosWrapper.get = (...params) =>
    interceptRequest(params, 'get', false);
  axiosWrapper.delete = (...params) =>
    interceptRequest(params, 'delete', false);
  axiosWrapper.head = (...params) =>
    interceptRequest(params, 'head', false);
  axiosWrapper.post = (...params) =>
    interceptRequest(params, 'post', true);
  axiosWrapper.put = (...params) =>
    interceptRequest(params, 'put', true);
  axiosWrapper.patch = (...params) =>
    interceptRequest(params, 'patch', true);

  // others
  axiosWrapper.all = targetAxios.all;
  axiosWrapper.spread = targetAxios.spread;
  axiosWrapper.interceptors = targetAxios.interceptors;
  axiosWrapper.defaults = targetAxios.defaults;

  axiosWrapper.targetAxios = targetAxios;

  return axiosWrapper;
}

function sendResponse(pushResponse, apiResponse) {
  const { status, data } = apiResponse;
  const headers = filterResponseHeaders(apiResponse.headers);

  pushResponse.writeHead(status, headers);
  if (data && data.pipe) {
    data.pipe(pushResponse);
  } else {
    pushResponse.end(data);
  }
}

function responseInterceptor(response) {
  // response = { status, statusText, headers, config, request, data }
  // response.config = { adapter, transformRequest, transformResponse,
  //    timeout, xsrfCookieName, xsrfHeaderName, maxContentLength,
  //    validateStatus, headers, method, url, data }
  const { config } = response;

  if (config.pushResponsePromise) {
    config.pushResponsePromise.then((pushResponse) => {
      if(pushResponse)
        sendResponse(pushResponse, response);
    });
  }
  return response;
}

function responseRejectedInterceptor(error) {
  // { code, errno, syscall, hostname, host, port, config, response } = error
  const { config, code, response } = error;
  if (config && config.pushResponsePromise) {
    config.pushResponsePromise.then((pushResponse) => {
      if (response && response.data) {
        sendResponse(pushResponse, response);
      } else {
        pushResponse.stream.destroy(code);
      }
    });
  }
  return Promise.reject(error);
}
<commit_msg>linting errors<commit_after>import url from 'url';
import { CancelToken } from 'axios';
import isAbsoluteUrl from 'axios/lib/helpers/isAbsoluteURL';
import combineURLs from 'axios/lib/helpers/combineURLs';
import getTargetAxios from './getTargetAxios';
import filterResponseHeaders from './filterResponseHeaders';

const pushableMethods = ['GET']; // TODO can I push_promise HEAD?

function canPush(pageResponse, requestURL, config) {
  return pageResponse.stream && pageResponse.stream.pushAllowed &&
    pushableMethods.includes(config.method.toUpperCase());
  // TODO also check domain
  // TODO don't push the same thing multiple times
}

// TODO should also give the option to disable all push requests and
//    instead just make requests as normal (for pre-rendering the html)
//    or ignore all requests (for after the response has been sent).

// for request that contain no data (GET, HEAD, DELETE)
function getRequestConfigWithoutData(method, [arg1, arg2]) {
  if (typeof arg1 === 'string') {
    const config = { ...arg2 };
    config.url = arg1;
    config.method = method || config.method || 'GET';
    return config;
  } else {
    return arg1;
  }
}
// for requests that contain data (POST, PUT)
function getRequestConfigWithData(method, [arg1, arg2, arg3]) {
  if (typeof arg1 === 'string') {
    const config = { ...arg3 };
    config.url = arg1;
    config.method = method || config.method || 'POST';
    return config;
  } else {
    return arg2 || arg1;
  }
}

function getWord(str) {
  const result = str && /\w+/.exec(str);
  return result && result[0];
}

/**
 * Wraps axios in something that will monitor requests/responses and
 * will issue push promises
 * @param {pageResponse} http2.Http2ServerResponse
 * @param {axiosParam} (optional) instance of axios, or axios config object
 * @return {object} returns an axios instance that will issue push promises automatically.
 */
export default function prepareAxios(pageResponse, axiosParam = null) {
  const targetAxios = getTargetAxios(axiosParam);

  if (global.window || !pageResponse) {
    // don't wrap it if on client side
    return targetAxios;
  }

  // Unfortunately, we can't use a real request interceptor.
  // Axios doesn't call its request interceptors immediately, so the
  // page response stream could close before we have a chance to send
  // the push promise.
  // This is why we have to wrap axios instead of just adding interceptors.

  function interceptRequest(params, method, hasData) {
    const config = hasData ?
      getRequestConfigWithData(method, params) :
      getRequestConfigWithoutData(method, params);

    const baseURL = targetAxios.defaults.baseURL || config.baseURL;
    const requestURLString = baseURL && !isAbsoluteUrl(config.url) ?
      combineURLs(baseURL, config.url) :
      config.url;
    const requestURL = url.parse(requestURLString);

    if (canPush(pageResponse, requestURL, config)) {
      // issue a push promise, with correct authority, path, and headers.
      // http/2 pseudo headers: :method, :path, :scheme, :authority
      const requestHeaders = {
        ...config.headers,
        ':path': requestURL.path,
        ':authority': requestURL.host,
        ':method': config.method.toUpperCase(),
        ':scheme': getWord(requestURL.protocol) || 'https'
      };

      const cancelSource = CancelToken.source();

      const pushResponsePromise = new Promise((resolve) => {
        pageResponse.createPushResponse(
          requestHeaders,
          (err, pushResponse) => {
            if (err) {
              // Can't reject the promise because nothing will catch() it.
              cancelSource.cancel('Push promise failed');
            } else {
              pushResponse.on('close', () => {
                cancelSource.cancel('Push stream closed');
              });
              resolve(pushResponse);
            }
          }
        );
      });

      const newConfig = {
        ...config,
        responseType: 'stream',
        cancelToken: cancelSource.token,
        pushResponsePromise
      };

      targetAxios.request(newConfig).catch(() => {});
      // TODO should the resulting promise be returned?
      //  Currently, I can't, because data is always a stream,
      //  but returning it could be useful for pushing any follow-up api calls.
    }
    // return an empty promise that never resolves.
    const emptyPromise = new Promise(() => {});
    emptyPromise.empty = true;
    return emptyPromise;
  }

  if (!targetAxios.usingIsomorphicPushInterceptors) {
    targetAxios.interceptors.response.use(
      responseInterceptor,
      responseRejectedInterceptor
    );
    targetAxios.usingIsomorphicPushInterceptors = true;
  }

  function axiosWrapper(...params) {
    return interceptRequest(params, null, false);
  }

  axiosWrapper.request = (...params) =>
    interceptRequest(params, null, false);
  axiosWrapper.get = (...params) =>
    interceptRequest(params, 'get', false);
  axiosWrapper.delete = (...params) =>
    interceptRequest(params, 'delete', false);
  axiosWrapper.head = (...params) =>
    interceptRequest(params, 'head', false);
  axiosWrapper.post = (...params) =>
    interceptRequest(params, 'post', true);
  axiosWrapper.put = (...params) =>
    interceptRequest(params, 'put', true);
  axiosWrapper.patch = (...params) =>
    interceptRequest(params, 'patch', true);

  // others
  axiosWrapper.all = targetAxios.all;
  axiosWrapper.spread = targetAxios.spread;
  axiosWrapper.interceptors = targetAxios.interceptors;
  axiosWrapper.defaults = targetAxios.defaults;

  axiosWrapper.targetAxios = targetAxios;

  return axiosWrapper;
}

function sendResponse(pushResponse, apiResponse) {
  const { status, data } = apiResponse;
  const headers = filterResponseHeaders(apiResponse.headers);

  pushResponse.writeHead(status, headers);
  if (data && data.pipe) {
    data.pipe(pushResponse);
  } else {
    pushResponse.end(data);
  }
}

function responseInterceptor(response) {
  // response = { status, statusText, headers, config, request, data }
  // response.config = { adapter, transformRequest, transformResponse,
  //    timeout, xsrfCookieName, xsrfHeaderName, maxContentLength,
  //    validateStatus, headers, method, url, data }
  const { config } = response;

  if (config.pushResponsePromise) {
    config.pushResponsePromise.then((pushResponse) => {
      if (pushResponse) {
        sendResponse(pushResponse, response);
      }
    });
  }
  return response;
}

function responseRejectedInterceptor(error) {
  // { code, errno, syscall, hostname, host, port, config, response } = error
  const { config, code, response } = error;
  if (config && config.pushResponsePromise) {
    config.pushResponsePromise.then((pushResponse) => {
      if (response && response.data) {
        sendResponse(pushResponse, response);
      } else {
        pushResponse.stream.destroy(code);
      }
    });
  }
  return Promise.reject(error);
}
<|endoftext|>


<commit_before>var fs = require('fs')
var path = require('path')
var exec = require('child_process').exec

module.exports = {
  isHusky: function(filename) {
    var data = fs.readFileSync(filename, 'utf-8')
    return data.indexOf('# husky') !== -1
  },

  hooksDir: function(callback) {
    exec('git rev-parse --git-dir', function(error, stdout, stderr) {
      if (error) {
        callback(stderr, null)
      } else {
        callback(null, stdout.trim() + '/hooks')
      }
    })
  },

  write: function(filename, data) {
    fs.writeFileSync(filename, data)
    fs.chmodSync(filename, 0755)
  },

  create: function(dir, name, cmd) {
    var filename = dir + '/' + name
    var data =
        '#!/bin/sh\n'
      + '# husky\n'

    // Needed on OS X / Linux when nvm is used and committing from Sublime Text
    if (process.platform !== 'win32') {
      data += 'PATH="' + process.env.PATH + '"\n'
    }

    // Assuming that this file is in node_modules/husky/src
    var packageDir = path.join(__dirname, '..', '..', '..')

    // dir being .git/hooks
    var projectDir = path.join(dir, '..', '..')

    // In order to support projects with package.json in a different directory
    // than .git, find relative path from project directory to package.json
    var relativePath = path.join('.', path.relative(projectDir, packageDir))

    data +=
        'cd .' + relativePath + '\n'
      + 'npm run --json | grep -q \'"' + cmd + '":\'\n' // fix for issue #16
      + 'if [ $? -ne 0 ]; then\n'
      + '  exit 0\n' // package.scripts[name] can't be found exit
      + 'fi\n'
      + 'npm run ' + cmd + ' --silent\n'
      + 'if [ $? -ne 0 ]; then\n'
      + '  echo\n'
      + '  echo "husky - ' + name + ' hook failed (add --no-verify to bypass)"\n'
      + '  echo\n'
      + '  exit 1\n'
      + 'fi\n'

    // Create hooks directory if needed
    if (!fs.existsSync(dir)) fs.mkdirSync(dir)

    // Create hook file
    if (!fs.existsSync(filename)) {
      this.write(filename, data)
    } else {
      if (this.isHusky(filename)) {
        this.write(filename, data)
      } else {
        console.log('  skipping .git/hooks/' + name + ' (existing user hook)')
      }
    }
  },

  remove: function(dir, name) {
    var filename = dir + '/' + name

    if (fs.existsSync(filename) && this.isHusky(filename)) {
      fs.unlinkSync(dir + '/' + name)
    }
  }
}
<commit_msg>Fix path<commit_after>var fs = require('fs')
var path = require('path')
var exec = require('child_process').exec

module.exports = {
  isHusky: function(filename) {
    var data = fs.readFileSync(filename, 'utf-8')
    return data.indexOf('# husky') !== -1
  },

  hooksDir: function(callback) {
    exec('git rev-parse --git-dir', function(error, stdout, stderr) {
      if (error) {
        callback(stderr, null)
      } else {
        callback(null, stdout.trim() + '/hooks')
      }
    })
  },

  write: function(filename, data) {
    fs.writeFileSync(filename, data)
    fs.chmodSync(filename, 0755)
  },

  create: function(dir, name, cmd) {
    var filename = dir + '/' + name
    var data =
        '#!/bin/sh\n'
      + '# husky\n'

    // Needed on OS X / Linux when nvm is used and committing from Sublime Text
    if (process.platform !== 'win32') {
      data += 'PATH="' + process.env.PATH + '"\n'
    }

    // Assuming that this file is in node_modules/husky/src
    var packageDir = path.join(__dirname, '..', '..', '..')

    // dir being .git/hooks
    var projectDir = path.join(dir, '..', '..')

    // In order to support projects with package.json in a different directory
    // than .git, find relative path from project directory to package.json
    var relativePath = path.join('.', path.relative(projectDir, packageDir))

    data +=
        'cd ' + relativePath + '\n'
      + 'npm run --json | grep -q \'"' + cmd + '":\'\n' // fix for issue #16
      + 'if [ $? -ne 0 ]; then\n'
      + '  exit 0\n' // package.scripts[name] can't be found exit
      + 'fi\n'
      + 'npm run ' + cmd + ' --silent\n'
      + 'if [ $? -ne 0 ]; then\n'
      + '  echo\n'
      + '  echo "husky - ' + name + ' hook failed (add --no-verify to bypass)"\n'
      + '  echo\n'
      + '  exit 1\n'
      + 'fi\n'

    // Create hooks directory if needed
    if (!fs.existsSync(dir)) fs.mkdirSync(dir)

    // Create hook file
    if (!fs.existsSync(filename)) {
      this.write(filename, data)
    } else {
      if (this.isHusky(filename)) {
        this.write(filename, data)
      } else {
        console.log('  skipping .git/hooks/' + name + ' (existing user hook)')
      }
    }
  },

  remove: function(dir, name) {
    var filename = dir + '/' + name

    if (fs.existsSync(filename) && this.isHusky(filename)) {
      fs.unlinkSync(dir + '/' + name)
    }
  }
}
<|endoftext|>


<commit_before>"use strict";

const _ = require("lodash"),
	Slack = require("./slack"),
	GenericParser = require("./parsers/generic"),
	parsers = _.map([
		"./parsers/cloudwatch",
		"./parsers/codecommit/pullrequest",
		"./parsers/codecommit/repository",
		"./parsers/autoscaling",
		"./parsers/aws-health",
		"./parsers/beanstalk",
		"./parsers/codebuild",
		"./parsers/codedeployCloudWatch",
		"./parsers/codedeploySns",
		"./parsers/codepipeline",
		"./parsers/codepipeline-approval",
		"./parsers/guardduty",
		"./parsers/inspector",
		"./parsers/rds",
		"./parsers/ses-received",
	], name => [name, require(name)]);

async function processIncoming(event) {
	if (_.isString(event)) {
		try {
			event = JSON.parse(event);
		}
		catch (err) {
			console.error(`Error parsing event JSON (continuing...): ${event}`);
		}
	}

	// Execute all parsers and use the first successful result
	let message;
	for (const i in parsers) {
		const parserName = parsers[i][0];
		try {
			message = await ((new parsers[i][1]()).parse(event));
			if (message) {
				// Truthy but empty message will stop execution
				if (message === true || _.isEmpty(message)) {
					console.error(`Parser stopping execution: ${parserName}`);
					return null;
				}
				break;
			}
		}
		catch (e) {
			console.error(`[Error parsing event][${parserName}] ${e}`);
		}
	}
	if (!message) {
		// Fallback to the generic parser if none other succeeded
		console.log("Falling back to GenericParser...");
		message = await (new GenericParser).parse(event);
	}

	return message;
}

module.exports.processEvent = processIncoming;

module.exports.handler = async (event, context, callback) => {
	context.callbackWaitsForEmptyEventLoop = false;
	console.log("Incoming Message:", JSON.stringify(event, null, 2));

	try {
		const message = await processIncoming(event);
		console.log("Sending Message to Slack:", JSON.stringify(message, null, 2));
		await Slack.postMessage(message);
		callback();
	}
	catch (e) {
		console.log("ERROR:", e);
		callback(e);
	}
};
<commit_msg>put GenericParser into parser list, log parser type after parsing succeeds<commit_after>"use strict";

const _ = require("lodash"),
	Slack = require("./slack"),
	parsers = _.map([
		// Ordered list of parsers:
		"cloudwatch",
		"codecommit/pullrequest",
		"codecommit/repository",
		"autoscaling",
		"aws-health",
		"beanstalk",
		"codebuild",
		"codedeployCloudWatch",
		"codedeploySns",
		"codepipeline",
		"codepipeline-approval",
		"guardduty",
		"inspector",
		"rds",
		"ses-received",
		// Last attempt to parse, will match any:
		"generic",
	], name => [name, require(`./parsers/${name}`)]);

let lastSelectedHandler;

async function processIncoming(event) {
	if (_.isString(event)) {
		try {
			event = JSON.parse(event);
		}
		catch (err) {
			console.error(`Error parsing event JSON (continuing...): ${event}`);
		}
	}

	// Execute all parsers and use the first successful result
	for (const i in parsers) {
		const parserName = parsers[i][0];
		try {
			const message = await ((new parsers[i][1]()).parse(event));
			if (message) {
				lastSelectedHandler = parserName;

				// Truthy but empty message will stop execution
				if (message === true || _.isEmpty(message)) {
					console.error(`Parser stopping execution: ${parserName}`);
					return null;// never send empty message
				}

				return message;
			}
		}
		catch (e) {
			console.error(`Error parsing event [parser:${parserName}]:`, e);
		}
	}
}

module.exports.processEvent = processIncoming;

module.exports.handler = async (event, context, callback) => {
	context.callbackWaitsForEmptyEventLoop = false;
	console.log("Incoming Message:", JSON.stringify(event, null, 2));

	try {
		const message = await processIncoming(event);
		if (message) {
			console.log(`Sending Slack message from Parser[${lastSelectedHandler}]:`, JSON.stringify(message, null, 2));
			await Slack.postMessage(message);
		}
		else {
			console.log("No parser matched event");
		}
		callback();
	}
	catch (e) {
		console.log("ERROR:", e);
		callback(e);
	}
};
<|endoftext|>


<commit_before>module.exports = {
  resolver: require('./resolver'),
  defaultListArgs: require('./defaultListArgs'),
  defaultArgs: require('./defaultArgs'),
  typeMapper: require('./typeMapper'),
  attributeFields: require('./attributeFields'),
  simplifyAST: require('./simplifyAST')
};
<commit_msg>expose Relay API<commit_after>module.exports = {
  resolver: require('./resolver'),
  defaultListArgs: require('./defaultListArgs'),
  defaultArgs: require('./defaultArgs'),
  typeMapper: require('./typeMapper'),
  attributeFields: require('./attributeFields'),
  simplifyAST: require('./simplifyAST'),
  relay: require('./relay')
};
<|endoftext|>


<commit_before>const configReducer = require('./reducers')

// these only get initialized during a call to init
let configurator
let commander

// if only 1 element is passed in,
// we can assume that its ok to
// do everything in one context?
// returns an event emitter than can be used to respond to keyboard events
/**
 * @params {Object} options - {
 *   @params {Element} target - element to mount configurator
 *   @params {HotkeyDefinitionsObject} hotkeys - hotkey definitions
 *   @param {Element} - engineListenerEl - defaults to window
 *   @returns {EventEmitter2}
 * }
 */
module.exports = (options) => {
  // configRootElement = window, engineListenerEl = window) => {
  // instantiate a store and pass it to our configurator and commander objects
  const store = configReducer(options.hotkeys)
  configurator = require('./configurator')(store)
  commander = require('./commander')(store)

  configurator.init(options.target)
  return commander(options.engineListenerEl || window)
}

/* TODO:
    When commander and configurator are running in seperate contexts
    we will assume its a chrome plugin, and allow each component to
    use its own store object, which will in turn be using chrome.storage.sync
    and its event emitter to transmit changes of the keymap.
*/

exports.Commander = (options) => {
  const store = configReducer(options.hotkeys)
  commander = require('./commander')(store)
  return commander(options.hotkeys, options.listenerEl)
}

exports.Configurator = (options) => {
  const store = configReducer(options.hotkeys)
  configurator = require('./configurator')(store)
  configurator.init(options.hotkeys, options.targetEl)
}

<commit_msg>Code cleanup and added comments for clarity.<commit_after>const configReducer = require('./reducers')

/**
 * @params {Object} options - {
 *   @params {Element} target - element to mount configurator
 *   @params {HotkeyDefinitionsObject} hotkeys - hotkey definitions
 *   @param {Element} - engineListenerEl - defaults to window
 *   @returns {EventEmitter2}
 * }
 */
module.exports = (options) => {
  // instantiate a redux store and pass it to our configurator and commander objects
  const store = configReducer(options.hotkeys)

  // import configurator and commander
  const configurator = require('./configurator')(store)
  const commander = require('./commander')(store)

  // init configurator
  configurator.init(options.target)

  // init commander and return the commander event emitter
  return commander(options.engineListenerEl || window)
}

/**
 *      DUAL CONTEXT API
 *
 *  use this api to create commander in
 *  one context and configurator in another
 *
 *  When using this method, each component will have
 *  its own store and so - commanders store
 *  will generate a new keymap when getting
 *  messages from chrome.storage.onChanged
 */
exports.Commander = (options) => {
  // create the redux store
  const store = configReducer(options.hotkeys)

  // import the commander object
  const commander = require('./commander')(store)

  // init commander and return his event emitter
  return commander(options.hotkeys, options.listenerEl)
}

exports.Configurator = (options) => {
  // create the redux store
  const store = configReducer(options.hotkeys)

  // import configurator
  const configurator = require('./configurator')(store)

  // init confiturator
  configurator.init(options.hotkeys, options.targetEl)
}

<|endoftext|>


<commit_before>#!/usr/bin/env node

var pollsProvider = require('./lib/pollsProvider');
var router = require('./lib/router').getInstance();
var sessionManager = require('./lib/sessionManager');
var auth = require('./lib/auth');

var http = require('http');
var fs = require('fs');
var path = require('path');

var FEEDBACKER_SESS='fdbckr-sess';
var UPDATE_DELAY=500; //min time between client updates.
http.globalAgent.maxSockets = 9999;

var currentPollId = 0;
var currentUser;
var currentPoll;
var currentQuestion = 0;
var clientResponses = [];
var userId = 0;
var usersCount = 0;
var lastUpdateClientsTs = 0;
var scheduledClientUpdate = null;

function getCurrentQuestion() {
    if (!currentPoll || !currentPoll.questions || !currentPoll.questions[currentQuestion]) {
        return null;
    }
    var currentQuestionObj = currentPoll.questions[currentQuestion];
    currentQuestionObj.usersCount = usersCount;
    return currentQuestionObj;
}

function updateClients(responses, object) {
    function doNotify() {
      lastUpdateClientsTs = Date.now();
      scheduledClientUpdate = null;
      console.log('===> NOW NOTIFYING');
      responses.forEach(function (response) {
        response.write('data: '+JSON.stringify(object)+'\n\n');
      });
    }

    var shouldUpdateImmediately = (Date.now() - lastUpdateClientsTs) >= UPDATE_DELAY;

    if (!scheduledClientUpdate && shouldUpdateImmediately) {
      console.log('===> IMMEDIATE NOTIFICATION');
      doNotify();
    } else if (!scheduledClientUpdate && !shouldUpdateImmediately) {
      console.log('===> SCHEDULING NOTIFICATION');
      scheduledClientUpdate = setTimeout(doNotify, UPDATE_DELAY);
    }
}

function getIndex(req, res) {
    console.log('%%%%%%%%%%%%%%%% cookies:', req.cookies);
    if (auth.isAuthorized(req)) {
        var page = fs.readFileSync(path.join(__dirname, 'page.html'), {encoding: 'utf8'});
        currentUser = req.parsedUrl.query.user;
        currentPoll = pollsProvider.initPoll(currentUser, currentPollId);
        var sessionId = sessionManager.newPresenterSession(currentUser);
        page = page.replace('<!-- session-id -->', sessionId);
        //TODO: render an html piece in a page.
        var presenterSectionHtml = fs.readFileSync(path.join(__dirname, 'presenterSection.html'), {encoding: 'utf8'});
        page = page.replace('<!-- presenter-sect -->', presenterSectionHtml);

        res.writeHead(200, {'Content-Type': 'text/html', 'Set-Cookie': FEEDBACKER_SESS+'='+sessionId});
        return res.end(page);
    } else {
        res.writeHead(401, {'Content-Type': 'text/html'});
        return res.end('Unauthorized');
    }
}

function getUserPage(req, res) {
    if (!getCurrentQuestion()) {
        res.writeHead(404, {'Content-Type': 'text/html'});
        res.end('<body>No presenter session initialized yet. </br> Please try later.<body/>');
        return;
    }

    console.log('========>>> generating user page.');
    userId += 1;
    var page = fs.readFileSync(path.join(__dirname, 'page.html'), {encoding: 'utf8'});
    page = page.replace('<!-- session-id -->', 'happy-user-'+userId);
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(page);
    return;
}

function removeElementFrom(arr, ob) {
  var idx = arr.indexOf(ob);
  if(idx != -1) {
    arr.splice(idx, 1);
  }
}

function getPoll(req, res) {
    console.log('%%%%%%%%%%%%%%%% cookies:', req.cookies);
    usersCount += 1;
    clientResponses.push(res);

    res.on('close', function(){
      usersCount -= 1;
      removeElementFrom(clientResponses, res);
    });
    res.on('finish', function(){
      usersCount -= 1;
      removeElementFrom(clientResponses, res);
    });

    console.log('>>>>', req.url, req.headers);
    res.writeHead(200, {'Content-Type': 'text/event-stream'});
    var currQuestion = getCurrentQuestion();
    console.log('#############################################################currQuestion', currQuestion);
    updateClients(clientResponses, currQuestion);
    return;
}

function postResponse (req, res) {
    var question = getCurrentQuestion();
    console.log('%%%%%%%%%%%%%%%% cookies:', req.cookies);
    var pollResponse = req.json;
    console.log('***** response: question:', pollResponse.question, 'response:', pollResponse.response);
    console.log('***** req.cookies:', req.headers.cookie);
    if(typeof pollResponse.question === 'number' && typeof pollResponse.response === 'number' ) {
        console.log('***** correct response: question:', pollResponse.question, 'response:', pollResponse.response);

        if(question.id === pollResponse.question && pollResponse.response <= question.responses.length - 1) {
            console.log('*** question and response found. counting.');
            question.responses[pollResponse.response].count += 1;
            updateClients(clientResponses, question);
            res.writeHead(200, "OK", {'Content-Type': 'text/html'});
        } else {
            var msg = "Not found: curr question: "+question.id+" max response: "+ question.responses.length -1;
            console.log('*** '+ msg);
            res.writeHead(404, msg, {'Content-Type': 'text/html'});
        }

    } else {
        var msg = ">>> could not parse question correctly :-(";
        console.log('====' + msg);
        res.writeHead(400, msg, {'Content-Type': 'text/html'});
    }
    res.end();

    return;

}

function postPagination(req, res){
    //curl -X POST --data '{"sessionId": "lksdjflsdkjf"}' http://localhost:8125/prev -v
    //curl -X POST --data '{"sessionId": "lksdjflsdkjf"}' http://localhost:8125/next -v
    var urlPath = req.parsedUrl.pathname;
    var isPageNextReq = urlPath.indexOf('next') !== -1;
    var pagingRequest = req.json;

    if (! sessionManager.getSession(pagingRequest.sessionId)) {
        var msg = '%%%==> pagingRequest NOT authorized: sessionId: '+pagingRequest.sessionId+' presenterId: ' + sessionManager.getSession(pagingRequest.sessionId);
        console.log(msg, pagingRequest);

        res.writeHead(401, msg, {'Content-Type': 'text/html'});
        return res.end();
    }

    console.log('%%%==> pagingRequest authorized', pagingRequest, urlPath);
    res.writeHead(204, "Done.", {'Content-Type': 'text/html'});
    res.end();
    if(isPageNextReq) {
        console.log('=========>>>>> NEXT');
        var numberOfPolls = currentPoll.questions.length;
        if(currentQuestion < numberOfPolls -1) {
            currentQuestion += 1;
        }
    } else {
        console.log('=========>>>>> PREV');
        if(currentQuestion > 0) {
            currentQuestion -= 1;
        }
    }
    updateClients(clientResponses, getCurrentQuestion());

    return;
}

router.get('/index.html', getIndex);
router.get('/poll', getPoll);
router.get('/join', getUserPage);
router.get('/', getIndex);
router.post('/response', postResponse);
router.post('/next', postPagination);
router.post('/prev', postPagination);

var server_port = process.env.OPENSHIFT_NODEJS_PORT || 8126;
var server_ip_address = process.env.OPENSHIFT_NODEJS_IP || "0.0.0.0";

http.createServer(router.route()).listen(server_port, server_ip_address);
console.log('Server running at http://'+server_ip_address+':'+server_port+'/');
<commit_msg>implement Post ping (authorized) in server<commit_after>#!/usr/bin/env node

var pollsProvider = require('./lib/pollsProvider');
var router = require('./lib/router').getInstance();
var sessionManager = require('./lib/sessionManager');
var auth = require('./lib/auth');

var http = require('http');
var fs = require('fs');
var path = require('path');

var FEEDBACKER_SESS='fdbckr-sess';
var UPDATE_DELAY=500; //min time between client updates.
http.globalAgent.maxSockets = 9999;

var currentPollId = 0;
var currentUser;
var currentPoll;
var currentQuestion = 0;
var clientResponses = [];
var userId = 0;
var usersCount = 0;
var lastUpdateClientsTs = 0;
var scheduledClientUpdate = null;

function getCurrentQuestion() {
    if (!currentPoll || !currentPoll.questions || !currentPoll.questions[currentQuestion]) {
        return null;
    }
    var currentQuestionObj = currentPoll.questions[currentQuestion];
    currentQuestionObj.usersCount = usersCount;
    return currentQuestionObj;
}

function updateClients(responses, object) {
    function doNotify() {
      lastUpdateClientsTs = Date.now();
      scheduledClientUpdate = null;
      console.log('===> NOW NOTIFYING');
      responses.forEach(function (response) {
        response.write('data: '+JSON.stringify(object)+'\n\n');
      });
    }

    var shouldUpdateImmediately = (Date.now() - lastUpdateClientsTs) >= UPDATE_DELAY;

    if (!scheduledClientUpdate && shouldUpdateImmediately) {
      console.log('===> IMMEDIATE NOTIFICATION');
      doNotify();
    } else if (!scheduledClientUpdate && !shouldUpdateImmediately) {
      console.log('===> SCHEDULING NOTIFICATION');
      scheduledClientUpdate = setTimeout(doNotify, UPDATE_DELAY);
    }
}

function getIndex(req, res) {
    console.log('%%%%%%%%%%%%%%%% cookies:', req.cookies);
    if (auth.isAuthorized(req)) {
        var page = fs.readFileSync(path.join(__dirname, 'page.html'), {encoding: 'utf8'});
        currentUser = req.parsedUrl.query.user;
        currentPoll = pollsProvider.initPoll(currentUser, currentPollId);
        var sessionId = sessionManager.newPresenterSession(currentUser);
        page = page.replace('<!-- session-id -->', sessionId);
        //TODO: render an html piece in a page.
        var presenterSectionHtml = fs.readFileSync(path.join(__dirname, 'presenterSection.html'), {encoding: 'utf8'});
        page = page.replace('<!-- presenter-sect -->', presenterSectionHtml);

        res.writeHead(200, {'Content-Type': 'text/html', 'Set-Cookie': FEEDBACKER_SESS+'='+sessionId});
        return res.end(page);
    } else {
        res.writeHead(401, {'Content-Type': 'text/html'});
        return res.end('Unauthorized');
    }
}

function getUserPage(req, res) {
    if (!getCurrentQuestion()) {
        res.writeHead(404, {'Content-Type': 'text/html'});
        res.end('<body>No presenter session initialized yet. </br> Please try later.<body/>');
        return;
    }

    console.log('========>>> generating user page.');
    userId += 1;
    var page = fs.readFileSync(path.join(__dirname, 'page.html'), {encoding: 'utf8'});
    page = page.replace('<!-- session-id -->', 'happy-user-'+userId);
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(page);
    return;
}

function removeElementFrom(arr, ob) {
  var idx = arr.indexOf(ob);
  if(idx != -1) {
    arr.splice(idx, 1);
  }
}

function getPoll(req, res) {
    console.log('%%%%%%%%%%%%%%%% cookies:', req.cookies);
    usersCount += 1;
    clientResponses.push(res);

    res.on('close', function(){
      usersCount -= 1;
      removeElementFrom(clientResponses, res);
    });
    res.on('finish', function(){
      usersCount -= 1;
      removeElementFrom(clientResponses, res);
    });

    console.log('>>>>', req.url, req.headers);
    res.writeHead(200, {'Content-Type': 'text/event-stream'});
    var currQuestion = getCurrentQuestion();
    console.log('#############################################################currQuestion', currQuestion);
    updateClients(clientResponses, currQuestion);
    return;
}

function postResponse (req, res) {
    var question = getCurrentQuestion();
    console.log('%%%%%%%%%%%%%%%% cookies:', req.cookies);
    var pollResponse = req.json;
    console.log('***** response: question:', pollResponse.question, 'response:', pollResponse.response);
    console.log('***** req.cookies:', req.headers.cookie);
    if(typeof pollResponse.question === 'number' && typeof pollResponse.response === 'number' ) {
        console.log('***** correct response: question:', pollResponse.question, 'response:', pollResponse.response);

        if(question.id === pollResponse.question && pollResponse.response <= question.responses.length - 1) {
            console.log('*** question and response found. counting.');
            question.responses[pollResponse.response].count += 1;
            updateClients(clientResponses, question);
            res.writeHead(200, "OK", {'Content-Type': 'text/html'});
        } else {
            var msg = "Not found: curr question: "+question.id+" max response: "+ question.responses.length -1;
            console.log('*** '+ msg);
            res.writeHead(404, msg, {'Content-Type': 'text/html'});
        }

    } else {
        var msg = ">>> could not parse question correctly :-(";
        console.log('====' + msg);
        res.writeHead(400, msg, {'Content-Type': 'text/html'});
    }
    res.end();

    return;

}

function postPing(req, res) {
  var urlPath = req.parsedUrl.pathname;
  var pingRequest = req.json;

  if (! sessionManager.getSession(pingRequest.sessionId)) {
      var msg = '%%%==> pingRequest NOT authorized: sessionId: '+pingRequest.sessionId;
      console.log(msg, pingRequest);

      res.writeHead(401, msg, {'Content-Type': 'text/html'});
      return res.end();
  }

  res.writeHead(200, "Done.", {'Content-Type': 'text/html'});
  res.end();

}

function postPagination(req, res){
    //curl -X POST --data '{"sessionId": "lksdjflsdkjf"}' http://localhost:8125/prev -v
    //curl -X POST --data '{"sessionId": "lksdjflsdkjf"}' http://localhost:8125/next -v
    var urlPath = req.parsedUrl.pathname;
    var isPageNextReq = urlPath.indexOf('next') !== -1;
    var pagingRequest = req.json;

    if (! sessionManager.getSession(pagingRequest.sessionId)) {
        var msg = '%%%==> pagingRequest NOT authorized: sessionId: '+pagingRequest.sessionId;
        console.log(msg, pagingRequest);

        res.writeHead(401, msg, {'Content-Type': 'text/html'});
        return res.end();
    }

    console.log('%%%==> pagingRequest authorized', pagingRequest, urlPath);
    res.writeHead(204, "Done.", {'Content-Type': 'text/html'});
    res.end();
    if(isPageNextReq) {
        console.log('=========>>>>> NEXT');
        var numberOfPolls = currentPoll.questions.length;
        if(currentQuestion < numberOfPolls -1) {
            currentQuestion += 1;
        }
    } else {
        console.log('=========>>>>> PREV');
        if(currentQuestion > 0) {
            currentQuestion -= 1;
        }
    }
    updateClients(clientResponses, getCurrentQuestion());

    return;
}

router.get('/index.html', getIndex);
router.get('/poll', getPoll);
router.get('/join', getUserPage);
router.get('/', getIndex);
router.post('/response', postResponse);
router.post('/next', postPagination);
router.post('/prev', postPagination);
router.post('/ping', postPing);

var server_port = process.env.OPENSHIFT_NODEJS_PORT || 8126;
var server_ip_address = process.env.OPENSHIFT_NODEJS_IP || "0.0.0.0";

http.createServer(router.route()).listen(server_port, server_ip_address);
console.log('Server running at http://'+server_ip_address+':'+server_port+'/');
<|endoftext|>