<commit_before>var Browser = {
  debug: 0,
  msgCallbackMissing: 'ERROR: Callback is missing',
  msgUnsupported: 'ERROR: Unsupported browser',

  name: 'Unknown', // Changed automatically at the end of this file

  detect: function() {
    if (navigator.userAgent.indexOf('Chrome') !== -1 && navigator.userAgent.indexOf('OPR') === -1)
      return 'Chrome';
    if (navigator.userAgent.indexOf('Chrome') !== -1 && navigator.userAgent.indexOf('OPR') !== -1)
      return 'Opera';
    console.log(this.msgUnsupported);
    return 'Unknown';
  },

  setIcon: function(path) {
    if (this.name == 'Chrome' || this.name == 'Opera') {
      if (chrome.browserAction != undefined) {
        chrome.browserAction.setIcon({path: path});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  setTitle: function(title) {
    if (this.name == 'Chrome' || this.name == 'Opera') {
      if (chrome.browserAction != undefined) {
        chrome.browserAction.setTitle({title: title});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  getBadgeText: function(callback) {
    if (typeof callback == 'undefined') {
      console.log(this.msgCallbackMissing);
    }
    else {
      if (this.name == 'Chrome' || this.name == 'Opera') {
        chrome.browserAction.getBadgeText({}, function(badgeText) {
          callback(badgeText);
        });
      }
      else {
        console.log(this.msgUnsupported);
      }
    }
  },

  setBadgeText: function(text) {
    if (typeof text == 'undefined' || text == null || isNaN(Number(text)) || Number(text) <= 0) {
      text = '';
    }
    if (this.name == 'Chrome' || this.name == 'Opera') {
      if (chrome.browserAction != undefined) {
        text = String(text);
        chrome.browserAction.setBadgeText({text: text});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  openTab: function(url) {
    if (this.name == 'Chrome' || this.name == 'Opera') {
      if (chrome.tabs != undefined) {
        chrome.tabs.create({url: url, selected: true});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  openBackgroundTab: function(url) {
    if (this.name == 'Chrome' || this.name == 'Opera') {
      if (chrome.tabs != undefined) {
        chrome.tabs.create({url: url, selected: false});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  getUrl: function(url) {
    // Allows you to get an accessible URL for a resource in the extension, e.g. an image
    if (this.name == 'Chrome' || this.name == 'Opera') {
      return chrome.extension.getURL(url);
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  getBackgroundProcess: function() {
    if (this.name == 'Chrome' || this.name == 'Opera') {
      if (chrome.extension != undefined) {
        return chrome.extension.getBackgroundPage();
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  getAppVersion: function() {
    try {
      if (this.name == 'Chrome' || this.name == 'Opera') {
        return chrome.app.getDetails().version;
      }
    } catch (err) {
      // Do nothing
    }
    console.log(this.msgUnsupported);
    return 'Unknown';
  },

  inProduction: function() {
    // Is the app in production? If so, there will be an update URL
    try {
      if (this.name == 'Chrome' || this.name == 'Opera') {
        return (typeof chrome.app.getDetails().update_url == "string");
      }
    } catch (err) {
      // Do nothing
    }
    console.log(this.msgUnsupported);
    return false; // assume dev mode
  },

  bindCommandHotkeys: function(affiliationWeb) {
    if (this.name == 'Chrome') {
      chrome.commands.onCommand.addListener(function(command) {
        if (command == 'open_instabart') {
          Browser.openTab('http://instabart.no');
        }
        else if (command == 'open_affiliation') {
          Browser.openTab(affiliationWeb);
        }
        else {
          console.log('ERROR: Unrecognized browser command');
        }
      });
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  // Things in item:
  // - feedKey: 'orgx'
  // - title: 'Hello World'
  // - description: 'Good day to you World, how are you today?'
  // - link: 'http://orgx.no/news/helloworld'
  // Optional things in item:
  // - image: 'http://orgx.no/media/helloworld.png'
  // - symbol: 'img/whatever.png'
  // - longStory: true
  // - stay: true
  createNotification: function(item) {
    // Check required params
    if (!item) console.log('ERROR: function takes one object, {feedKey, title, description, link, *image, *symbol, *longStory, *stay} (* == optional)');
    if (!item.feedKey) console.log('ERROR: item.feedKey is required');
    if (!item.title) console.log('ERROR: item.title is required');
    if (!item.description) console.log('ERROR: item.description is required');
    if (!item.link) console.log('ERROR: item.link is required');

    var self = this;
    if (this.name == 'Chrome') {
      // Check if browser is active, not "idle" or "locked"
      if (chrome.idle) {
        chrome.idle.queryState(30, function (state) {
          if (state == 'active') {

            // Load affiliation icon if symbol is not provided
            if (!item.symbol)
              item.symbol = Affiliation.org[item.feedKey].symbol;

            // Set notification
            var notification = {
               type: 'basic',
               iconUrl: item.symbol,
               title: item.title,
               message: item.description,
               priority: 0,
            };

            // We'll show an "image"-type notification if image exists and is not a placeholder
            if (item.image) {
              if (item.image != Affiliation.org[item.feedKey].placeholder) {
                notification.type = 'image';
                notification.imageUrl = item.image;
              }
            }

            // Shorten messages to fit nicely (300 because around 250 is max limit anyway)
            var maxLength = (item.longStory ? 300 : 63);
            if (maxLength < item.description.length) {
              notification.message = item.description.substring(0, maxLength) + '...';
            }

            // If basic type is used, we should also provide expandedMessage
            if (notification.type == 'basic') {
              notification.expandedMessage = item.description;
              var expandedMaxLength = (item.longStory ? 300 : 180);
              if (expandedMaxLength < item.description.length) {
                notification.expandedMessage = item.description.substring(0, expandedMaxLength) + '...';
              }
            }

            // Generate random ID
            var id = String(Math.round(Math.random()*100000));

            // Save the link to make the notification clickable. Associating the id with the
            // window object instead of putting it in localStorage makes sure all links are
            // cleared every time the background process restarts, rather than filling up
            // localStorage with links.
            window[id] = item.link;

            // Show the notification
            chrome.notifications.create(id, notification, function(notID) {
              if (self.debug) console.log('Succesfully created notification with ID', notID);
            });
            // Choose how long the notification stays around for
            // if stay? 10 minutes
            // if longStory? 10 seconds
            // else 5 seconds
            // Note: Chrom(e|ium) on Linux doesn't remove the notification,
            // automatically, so it needs to be manually cleared.
            var timeout = (item.stay ? 600000 : (item.longStory ? 10000 : 5000));
            setTimeout(function() {
              chrome.notifications.clear(id, function(wasCleared) {
                if (self.debug) console.log('Cleared notification?', wasCleared);
              });
            }, timeout);
          }
          else {
            if (self.debug) console.log('Notification not sent, state was', state);
          }
        });
      }
      else {
        if (self.debug) console.log('ERROR: This version of Chrome does not support chrome.idle');
      }
    }
    else if (this.name == 'Opera') {
      // Desktop Notifications not yet available
      if (self.debug) console.log('BROWSER.JS: createNotification, not yet avaliable in Opera');
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  // Event handlers for the various notification events

  registerNotificationListeners: function() {
    if (this.name == 'Chrome') {
      window.addEventListener("load", function() {
        chrome.notifications.onClosed.addListener(Browser.notificationClosed);
        chrome.notifications.onClicked.addListener(Browser.notificationClicked);
        chrome.notifications.onButtonClicked.addListener(Browser.notificationBtnClick);
      });
    }
    else if (this.name == 'Opera') {
      console.log(this.msgUnsupported);
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  notificationClosed: function(notID, byUser) {
    if (byUser) {
      Analytics.trackEvent('closeNotification', 'byUser');
    }
    else {
      Analytics.trackEvent('closeNotification', 'automatic');
    }
  },

  notificationClicked: function(notID) {
    var link = window[notID];
    Analytics.trackEvent('clickNotification', link);
    Browser.openTab(link);
  },

  notificationBtnClick: function(notID, iBtn) {
    Analytics.trackEvent('clickNotificationButton', iBtn);
  },

  bindOmniboxToOracle: function() {
    if (this.name == 'Chrome' || this.name == 'Opera') {
      // This event is fired each time the user updates the text in the omnibox,
      // as long as the extension's keyword mode is still active.
      // chrome.omnibox.onInputChanged.addListener(function(text, suggest) {
      //   if (Browser.debug) console.log('inputChanged: ' + text);
      //   suggest([
      //     {content: text + " one", description: text + " the first one"},
      //     {content: text + " number two", description: text + " the second entry"}
      //   ]);
      // });
      // This event is fired with the user accepts the input in the omnibox.
      chrome.omnibox.onInputEntered.addListener(function(text) {
        // console.log('inputEntered: ' + text);
        Oracle.ask(text, function(answer) {
          answer = answer.replace(/@/g, '\n');
          if (Browser.debug) console.log('oracle answer: ' + answer);
          Analytics.trackEvent('oracleOmniboxAnswer');
          // Browser.createNotification
          //   'feedKey': ls.affiliationKey1
          //   'title': 'Orakelet'
          //   'description': answer
          //   'link': 'http://atb.no'
          //   'longStory': true
          //   'stay': true
          alert(answer);
        });
      });
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

}

// Detect and set name
Browser.name = Browser.detect();
<commit_msg>browser: improved production detection check<commit_after>var Browser = {
  debug: 0,
  msgCallbackMissing: 'ERROR: Callback is missing',
  msgUnsupported: 'ERROR: Unsupported browser',

  name: 'Unknown', // Changed automatically at the end of this file

  detect: function() {
    if (navigator.userAgent.indexOf('Chrome') !== -1 && navigator.userAgent.indexOf('OPR') === -1)
      return 'Chrome';
    if (navigator.userAgent.indexOf('Chrome') !== -1 && navigator.userAgent.indexOf('OPR') !== -1)
      return 'Opera';
    console.log(this.msgUnsupported);
    return 'Unknown';
  },

  setIcon: function(path) {
    if (this.name == 'Chrome' || this.name == 'Opera') {
      if (chrome.browserAction != undefined) {
        chrome.browserAction.setIcon({path: path});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  setTitle: function(title) {
    if (this.name == 'Chrome' || this.name == 'Opera') {
      if (chrome.browserAction != undefined) {
        chrome.browserAction.setTitle({title: title});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  getBadgeText: function(callback) {
    if (typeof callback == 'undefined') {
      console.log(this.msgCallbackMissing);
    }
    else {
      if (this.name == 'Chrome' || this.name == 'Opera') {
        chrome.browserAction.getBadgeText({}, function(badgeText) {
          callback(badgeText);
        });
      }
      else {
        console.log(this.msgUnsupported);
      }
    }
  },

  setBadgeText: function(text) {
    if (typeof text == 'undefined' || text == null || isNaN(Number(text)) || Number(text) <= 0) {
      text = '';
    }
    if (this.name == 'Chrome' || this.name == 'Opera') {
      if (chrome.browserAction != undefined) {
        text = String(text);
        chrome.browserAction.setBadgeText({text: text});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  openTab: function(url) {
    if (this.name == 'Chrome' || this.name == 'Opera') {
      if (chrome.tabs != undefined) {
        chrome.tabs.create({url: url, selected: true});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  openBackgroundTab: function(url) {
    if (this.name == 'Chrome' || this.name == 'Opera') {
      if (chrome.tabs != undefined) {
        chrome.tabs.create({url: url, selected: false});
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  getUrl: function(url) {
    // Allows you to get an accessible URL for a resource in the extension, e.g. an image
    if (this.name == 'Chrome' || this.name == 'Opera') {
      return chrome.extension.getURL(url);
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  getBackgroundProcess: function() {
    if (this.name == 'Chrome' || this.name == 'Opera') {
      if (chrome.extension != undefined) {
        return chrome.extension.getBackgroundPage();
      }
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  getAppVersion: function() {
    try {
      if (this.name == 'Chrome' || this.name == 'Opera') {
        return chrome.app.getDetails().version;
      }
    } catch (err) {
      // Do nothing
    }
    console.log(this.msgUnsupported);
    return 'Unknown';
  },

  inProduction: function() {
    // Is the app in production? If so, there will be an update URL
    try {
      if (this.name === 'Chrome' || this.name === 'Opera') {
        if (chrome.app.getDetails().id === 'hfgffimlnajpbenfpaofmmffcdmgkllf') {
          return true;
        }
        else if (chrome.app.getDetails().id === 'npnpbddfcaibgnegafofkmffmbmflelj') {
          return true;
        }
        else if (typeof chrome.app.getDetails().update_url === 'string') {
          return true;
        }
        else {
          return false;
        }
      }
    } catch (err) {
      // Do nothing
    }
    console.log(this.msgUnsupported);
    return false; // assume dev mode
  },

  bindCommandHotkeys: function(affiliationWeb) {
    if (this.name == 'Chrome') {
      chrome.commands.onCommand.addListener(function(command) {
        if (command == 'open_instabart') {
          Browser.openTab('http://instabart.no');
        }
        else if (command == 'open_affiliation') {
          Browser.openTab(affiliationWeb);
        }
        else {
          console.log('ERROR: Unrecognized browser command');
        }
      });
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  // Things in item:
  // - feedKey: 'orgx'
  // - title: 'Hello World'
  // - description: 'Good day to you World, how are you today?'
  // - link: 'http://orgx.no/news/helloworld'
  // Optional things in item:
  // - image: 'http://orgx.no/media/helloworld.png'
  // - symbol: 'img/whatever.png'
  // - longStory: true
  // - stay: true
  createNotification: function(item) {
    // Check required params
    if (!item) console.log('ERROR: function takes one object, {feedKey, title, description, link, *image, *symbol, *longStory, *stay} (* == optional)');
    if (!item.feedKey) console.log('ERROR: item.feedKey is required');
    if (!item.title) console.log('ERROR: item.title is required');
    if (!item.description) console.log('ERROR: item.description is required');
    if (!item.link) console.log('ERROR: item.link is required');

    var self = this;
    if (this.name == 'Chrome') {
      // Check if browser is active, not "idle" or "locked"
      if (chrome.idle) {
        chrome.idle.queryState(30, function (state) {
          if (state == 'active') {

            // Load affiliation icon if symbol is not provided
            if (!item.symbol)
              item.symbol = Affiliation.org[item.feedKey].symbol;

            // Set notification
            var notification = {
               type: 'basic',
               iconUrl: item.symbol,
               title: item.title,
               message: item.description,
               priority: 0,
            };

            // We'll show an "image"-type notification if image exists and is not a placeholder
            if (item.image) {
              if (item.image != Affiliation.org[item.feedKey].placeholder) {
                notification.type = 'image';
                notification.imageUrl = item.image;
              }
            }

            // Shorten messages to fit nicely (300 because around 250 is max limit anyway)
            var maxLength = (item.longStory ? 300 : 63);
            if (maxLength < item.description.length) {
              notification.message = item.description.substring(0, maxLength) + '...';
            }

            // If basic type is used, we should also provide expandedMessage
            if (notification.type == 'basic') {
              notification.expandedMessage = item.description;
              var expandedMaxLength = (item.longStory ? 300 : 180);
              if (expandedMaxLength < item.description.length) {
                notification.expandedMessage = item.description.substring(0, expandedMaxLength) + '...';
              }
            }

            // Generate random ID
            var id = String(Math.round(Math.random()*100000));

            // Save the link to make the notification clickable. Associating the id with the
            // window object instead of putting it in localStorage makes sure all links are
            // cleared every time the background process restarts, rather than filling up
            // localStorage with links.
            window[id] = item.link;

            // Show the notification
            chrome.notifications.create(id, notification, function(notID) {
              if (self.debug) console.log('Succesfully created notification with ID', notID);
            });
            // Choose how long the notification stays around for
            // if stay? 10 minutes
            // if longStory? 10 seconds
            // else 5 seconds
            // Note: Chrom(e|ium) on Linux doesn't remove the notification,
            // automatically, so it needs to be manually cleared.
            var timeout = (item.stay ? 600000 : (item.longStory ? 10000 : 5000));
            setTimeout(function() {
              chrome.notifications.clear(id, function(wasCleared) {
                if (self.debug) console.log('Cleared notification?', wasCleared);
              });
            }, timeout);
          }
          else {
            if (self.debug) console.log('Notification not sent, state was', state);
          }
        });
      }
      else {
        if (self.debug) console.log('ERROR: This version of Chrome does not support chrome.idle');
      }
    }
    else if (this.name == 'Opera') {
      // Desktop Notifications not yet available
      if (self.debug) console.log('BROWSER.JS: createNotification, not yet avaliable in Opera');
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  // Event handlers for the various notification events

  registerNotificationListeners: function() {
    if (this.name == 'Chrome') {
      window.addEventListener("load", function() {
        chrome.notifications.onClosed.addListener(Browser.notificationClosed);
        chrome.notifications.onClicked.addListener(Browser.notificationClicked);
        chrome.notifications.onButtonClicked.addListener(Browser.notificationBtnClick);
      });
    }
    else if (this.name == 'Opera') {
      console.log(this.msgUnsupported);
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

  notificationClosed: function(notID, byUser) {
    if (byUser) {
      Analytics.trackEvent('closeNotification', 'byUser');
    }
    else {
      Analytics.trackEvent('closeNotification', 'automatic');
    }
  },

  notificationClicked: function(notID) {
    var link = window[notID];
    Analytics.trackEvent('clickNotification', link);
    Browser.openTab(link);
  },

  notificationBtnClick: function(notID, iBtn) {
    Analytics.trackEvent('clickNotificationButton', iBtn);
  },

  bindOmniboxToOracle: function() {
    if (this.name == 'Chrome' || this.name == 'Opera') {
      // This event is fired each time the user updates the text in the omnibox,
      // as long as the extension's keyword mode is still active.
      // chrome.omnibox.onInputChanged.addListener(function(text, suggest) {
      //   if (Browser.debug) console.log('inputChanged: ' + text);
      //   suggest([
      //     {content: text + " one", description: text + " the first one"},
      //     {content: text + " number two", description: text + " the second entry"}
      //   ]);
      // });
      // This event is fired with the user accepts the input in the omnibox.
      chrome.omnibox.onInputEntered.addListener(function(text) {
        // console.log('inputEntered: ' + text);
        Oracle.ask(text, function(answer) {
          answer = answer.replace(/@/g, '\n');
          if (Browser.debug) console.log('oracle answer: ' + answer);
          Analytics.trackEvent('oracleOmniboxAnswer');
          // Browser.createNotification
          //   'feedKey': ls.affiliationKey1
          //   'title': 'Orakelet'
          //   'description': answer
          //   'link': 'http://atb.no'
          //   'longStory': true
          //   'stay': true
          alert(answer);
        });
      });
    }
    else {
      console.log(this.msgUnsupported);
    }
  },

}

// Detect and set name
Browser.name = Browser.detect();
<|endoftext|>


<commit_before>var express = require("express");
var path = require("path");
var bodyParser = require("body-parser");
var mongodb = require("mongodb");
var ObjectID = mongodb.ObjectID;

var CONTACTS_COLLECTION = "contacts";

var app = express();
app.use(express.static(__dirname + "/public"));
app.use(bodyParser.json());

// Create a database variable outside of the database connection callback to reuse the connection pool in your app.
var db;

// Connect to the database before starting the application server.
mongodb.MongoClient.connect(process.env.MONGODB_URI, function (err, database) {
  if (err) {
    console.log(err);
    process.exit(1);
  }

  // Save database object from the callback for reuse.
  db = database;
  console.log("Database connection ready");

  // Initialize the app.
  var server = app.listen(process.env.PORT || 8080, function () {
    var port = server.address().port;
    console.log("App now running on port", port);
  });
});

// CONTACTS API ROUTES BELOW
<commit_msg>Sters db<commit_after>var express = require("express");
var path = require("path");
var bodyParser = require("body-parser");
var app = express();

var server = app.listen(process.env.PORT || 8080, function () {
  var port = server.address().port;
  console.log("App now running on port", port);
});

// CONTACTS API ROUTES BELOW
<|endoftext|>


<commit_before>import GraphQLJSClient from './graphql-client';
import Config from './config';
import ProductResource from './product-resource';
import CollectionResource from './collection-resource';
import ShopResource from './shop-resource';
import CheckoutResource from './checkout-resource';
import ImageResource from './image-resource';
import {version} from '../package.json';

// GraphQL
import types from '../schema.json';

/**
 * The JS Buy SDK Client.
 * @class
 *
 * @property {ProductResource} product The property under which product fetching methods live.
 * @property {CollectionResource} collection The property under which collection fetching methods live.
 * @property {ShopResource} shop The property under which shop fetching methods live.
 * @property {CheckoutResource} checkout The property under which shop fetching and mutating methods live.
 * @property {ImageResource} image The property under which image helper methods live.
 */
class Client {

  /**
   * Primary entry point for building a new Client.
   */
  static buildClient(config, fetchFunction) {
    const newConfig = new Config(config);

    return new Client(newConfig, GraphQLJSClient, fetchFunction);
  }

  /**
   * @constructs Client
   * @param {Config} config An instance of {@link Config} used to configure the Client.
   */
  constructor(config, GraphQLClientClass = GraphQLJSClient, fetchFunction) {
    const url = `https://${config.domain}/api/graphql`;

    const headers = {
      'X-SDK-Variant': 'javascript',
      'X-SDK-Version': version,
      'X-Shopify-Storefront-Access-Token': config.storefrontAccessToken
    };

    if (fetchFunction) {
      headers['Content-Type'] = 'application/json';
      headers.Accept = 'application/json';

      this.graphQLClient = new GraphQLClientClass(types, {
        fetcher: function fetcher(graphQLParams) {
          return fetchFunction(url, {
            body: JSON.stringify(graphQLParams),
            method: 'POST',
            mode: 'cors',
            headers
          }).then((response) => response.json());
        }
      });
    } else {
      this.graphQLClient = new GraphQLClientClass(types, {
        url,
        fetcherOptions: {headers}
      });
    }

    this.product = new ProductResource(this.graphQLClient);
    this.collection = new CollectionResource(this.graphQLClient);
    this.shop = new ShopResource(this.graphQLClient);
    this.checkout = new CheckoutResource(this.graphQLClient);
    this.image = new ImageResource(this.graphQLClient);
  }

  /**
   * Fetches the next page of models
   *
   * @example
   * client.fetchNextPage(products).then((nextProducts) => {
   *   // Do something with the products
   * });
   *
   * @param {models} [Array] The paginated set to fetch the next page of
   * @return {Promise|GraphModel[]} A promise resolving with an array of `GraphModel`s of the type provided.
   */
  fetchNextPage(models) {
    return this.graphQLClient.fetchNextPage(models);
  }
}

export default Client;
<commit_msg>expose config as a key on client<commit_after>import GraphQLJSClient from './graphql-client';
import Config from './config';
import ProductResource from './product-resource';
import CollectionResource from './collection-resource';
import ShopResource from './shop-resource';
import CheckoutResource from './checkout-resource';
import ImageResource from './image-resource';
import {version} from '../package.json';

// GraphQL
import types from '../schema.json';

/**
 * The JS Buy SDK Client.
 * @class
 *
 * @property {ProductResource} product The property under which product fetching methods live.
 * @property {CollectionResource} collection The property under which collection fetching methods live.
 * @property {ShopResource} shop The property under which shop fetching methods live.
 * @property {CheckoutResource} checkout The property under which shop fetching and mutating methods live.
 * @property {ImageResource} image The property under which image helper methods live.
 */
class Client {

  /**
   * Primary entry point for building a new Client.
   */
  static buildClient(config, fetchFunction) {
    const newConfig = new Config(config);
    const client = new Client(newConfig, GraphQLJSClient, fetchFunction);

    client.config = newConfig;

    return client;
  }

  /**
   * @constructs Client
   * @param {Config} config An instance of {@link Config} used to configure the Client.
   */
  constructor(config, GraphQLClientClass = GraphQLJSClient, fetchFunction) {
    const url = `https://${config.domain}/api/graphql`;

    const headers = {
      'X-SDK-Variant': 'javascript',
      'X-SDK-Version': version,
      'X-Shopify-Storefront-Access-Token': config.storefrontAccessToken
    };

    if (fetchFunction) {
      headers['Content-Type'] = 'application/json';
      headers.Accept = 'application/json';

      this.graphQLClient = new GraphQLClientClass(types, {
        fetcher: function fetcher(graphQLParams) {
          return fetchFunction(url, {
            body: JSON.stringify(graphQLParams),
            method: 'POST',
            mode: 'cors',
            headers
          }).then((response) => response.json());
        }
      });
    } else {
      this.graphQLClient = new GraphQLClientClass(types, {
        url,
        fetcherOptions: {headers}
      });
    }

    this.product = new ProductResource(this.graphQLClient);
    this.collection = new CollectionResource(this.graphQLClient);
    this.shop = new ShopResource(this.graphQLClient);
    this.checkout = new CheckoutResource(this.graphQLClient);
    this.image = new ImageResource(this.graphQLClient);
  }

  /**
   * Fetches the next page of models
   *
   * @example
   * client.fetchNextPage(products).then((nextProducts) => {
   *   // Do something with the products
   * });
   *
   * @param {models} [Array] The paginated set to fetch the next page of
   * @return {Promise|GraphModel[]} A promise resolving with an array of `GraphModel`s of the type provided.
   */
  fetchNextPage(models) {
    return this.graphQLClient.fetchNextPage(models);
  }
}

export default Client;
<|endoftext|>


<commit_before>'use strict';

var request = require('supertest');
var load = require('./fixtures/load');
var bootevent = require('../lib/server/bootevent');
var language = require('../lib/language')();
require('should');

describe('Entries REST api', function ( ) {
  var entries = require('../lib/api/entries/');

  this.timeout(10000);
  before(function (done) {
    var env = require('../env')( );
    env.settings.authDefaultRoles = 'readable';
    this.wares = require('../lib/middleware/')(env);
    this.archive = null;
    this.app = require('express')( );
    this.app.enable('api');
    var self = this;
    bootevent(env, language).boot(function booted (ctx) {
      self.app.use('/', entries(self.app, self.wares, ctx));
      self.archive = require('../lib/server/entries')(env, ctx);

      var creating = load('json');
      creating.push({type: 'sgv', sgv: 100, date: Date.now()});
      self.archive.create(creating, done);
    });
  });

  beforeEach(function (done) {
    var creating = load('json');
    creating.push({type: 'sgv', sgv: 100, date: Date.now()});
    this.archive.create(creating, done);
  });

  afterEach(function (done) {
    this.archive( ).remove({ }, done);
  });

  after(function (done) {
    this.archive( ).remove({ }, done);
  });

  // keep this test pinned at or near the top in order to validate all
  // entries successfully uploaded. if res.body.length is short of the
  // expected value, it may indicate a regression in the create
  // function callback logic in entries.js.
  it('gets requested number of entries', function (done) {
    var count = 30;
    request(this.app)
      .get('/entries.json?find[dateString][$gte]=2014-07-19&count=' + count)
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(count);
        done();
      });
  });

  it('gets default number of entries', function (done) {
    var defaultCount = 10;
    request(this.app)
      .get('/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(defaultCount);
        done( );
      });
  });

  it('gets entries in right order', function (done) {
    var defaultCount = 10;
    request(this.app)
      .get('/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(defaultCount);

        var array = res.body;
        var firstEntry = array[0];
        var secondEntry = array[1];

        firstEntry.date.should.be.above(secondEntry.date);

        done( );
      });
  });

  it('/echo/ api shows query', function (done) {
    request(this.app)
      .get('/echo/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Object);
        res.body.query.should.be.instanceof(Object);
        res.body.input.should.be.instanceof(Object);
        res.body.input.find.should.be.instanceof(Object);
        res.body.storage.should.equal('entries');
        done( );
      });
  });

  it('/slice/ can slice time', function (done) {
    var app = this.app;
    request(app)
      .get('/slice/entries/dateString/sgv/2014-07.json?count=20')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(20);
        done( );
      });
  });

  it('/times/echo can describe query', function (done) {
    var app = this.app;
    request(app)
      .get('/times/echo/2014-07/.*T{00..05}:.json?count=20&find[sgv][$gte]=160')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Object);
        res.body.req.should.have.property('query');
        res.body.should.have.property('pattern').with.lengthOf(6);
        done( );
      });
  });

  it('/slice/ can slice with multiple prefix', function (done) {
    var app = this.app;
    request(app)
      .get('/slice/entries/dateString/sgv/2014-07-{17..20}.json?count=20')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(20);
        done( );
      });
  });

  it('/slice/ can slice time with prefix and no results', function (done) {
    var app = this.app;
    request(app)
      .get('/slice/entries/dateString/sgv/1999-07.json?count=20&find[sgv][$lte]=401')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(0);
        done( );
      });
  });

  it('/times/ can get modal times', function (done) {
    var app = this.app;
    request(app)
      .get('/times/2014-07-/{0..30}T.json?')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(10);
        done( );
      });
  });

  it('/times/ can get modal minutes and times', function (done) {
    var app = this.app;
    request(app)
      .get('/times/20{14..15}-07/T{09..10}.json?')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(10);
        done( );
      });
  });
  it('/times/ can get multiple prefixen and modal minutes and times', function (done) {
    var app = this.app;
    request(app)
      .get('/times/20{14..15}/T.*:{00..60}.json?')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(10);
        done( );
      });
  });

  it('/entries/current.json', function (done) {
    request(this.app)
      .get('/entries/current.json')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(1);
        res.body[0].sgv.should.equal(100);
        done();
      });
  });

  it('/entries/:id', function (done) {
    var app = this.app;
    this.archive.list({count: 1}, function(err, records) {
      var currentId = records.pop()._id.toString();
      request(app)
        .get('/entries/'+currentId+'.json')
        .expect(200)
        .end(function (err, res) {
          res.body.should.be.instanceof(Array).and.have.lengthOf(1);
          res.body[0]._id.should.equal(currentId);
          done( );
        });
      });
    });

  it('/entries/:model', function (done) {
    var app = this.app;
    request(app)
      .get('/entries/sgv/.json?count=10&find[dateString][$gte]=2014')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(10);
        done( );
      });
  });

  it('disallow POST by readable /entries/preview', function (done) {
    request(this.app)
      .post('/entries/preview.json')
      .send(load('json'))
      .expect(401)
      .end(function (err, res) {
        // res.body.should.be.instanceof(Array).and.have.lengthOf(30);
        done();
      });
  });

  it('disallow deletes unauthorized', function (done) {
    var app = this.app;

    request(app)
      .delete('/entries/sgv?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20')
      .expect(401)
      .end(function (err) {
        if (err) {
          done(err);
        } else {
          request(app)
            .get('/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20')
            .expect(200)
            .end(function (err, res) {
              res.body.should.be.instanceof(Array).and.have.lengthOf(10);
              done();
            });
        }
      });
  });

});
<commit_msg>Add insert, query, delete test for entries api<commit_after>'use strict';

var request = require('supertest');
var load = require('./fixtures/load');
var bootevent = require('../lib/server/bootevent');
var language = require('../lib/language')();
require('should');

describe('Entries REST api', function ( ) {
  var entries = require('../lib/api/entries/');
  var self = this;

  this.timeout(10000);
  before(function (done) {
    self.env = require('../env')( );
    self.env.settings.authDefaultRoles = 'readable';
    self.wares = require('../lib/middleware/')(self.env);
    self.archive = null;
    self.app = require('express')( );
    self.app.enable('api');
    bootevent(self.env, language).boot(function booted (ctx) {
      self.app.use('/', entries(self.app, self.wares, ctx));
      self.archive = require('../lib/server/entries')(self.env, ctx);

      var creating = load('json');
      creating.push({type: 'sgv', sgv: 100, date: Date.now()});
      self.archive.create(creating, done);
    });
  });

  beforeEach(function (done) {
    var creating = load('json');
    creating.push({type: 'sgv', sgv: 100, date: Date.now()});
    self.archive.create(creating, done);
  });

  afterEach(function (done) {
    self.archive( ).remove({ }, done);
  });

  after(function (done) {
    self.archive( ).remove({ }, done);
  });

  // keep this test pinned at or near the top in order to validate all
  // entries successfully uploaded. if res.body.length is short of the
  // expected value, it may indicate a regression in the create
  // function callback logic in entries.js.
  it('gets requested number of entries', function (done) {
    var count = 30;
    request(self.app)
      .get('/entries.json?find[dateString][$gte]=2014-07-19&count=' + count)
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(count);
        done();
      });
  });

  it('gets default number of entries', function (done) {
    var defaultCount = 10;
    request(self.app)
      .get('/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(defaultCount);
        done( );
      });
  });

  it('gets entries in right order', function (done) {
    var defaultCount = 10;
    request(self.app)
      .get('/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(defaultCount);

        var array = res.body;
        var firstEntry = array[0];
        var secondEntry = array[1];

        firstEntry.date.should.be.above(secondEntry.date);

        done( );
      });
  });

  it('/echo/ api shows query', function (done) {
    request(self.app)
      .get('/echo/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Object);
        res.body.query.should.be.instanceof(Object);
        res.body.input.should.be.instanceof(Object);
        res.body.input.find.should.be.instanceof(Object);
        res.body.storage.should.equal('entries');
        done( );
      });
  });

  it('/slice/ can slice time', function (done) {
    var app = self.app;
    request(app)
      .get('/slice/entries/dateString/sgv/2014-07.json?count=20')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(20);
        done( );
      });
  });

  it('/times/echo can describe query', function (done) {
    var app = self.app;
    request(app)
      .get('/times/echo/2014-07/.*T{00..05}:.json?count=20&find[sgv][$gte]=160')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Object);
        res.body.req.should.have.property('query');
        res.body.should.have.property('pattern').with.lengthOf(6);
        done( );
      });
  });

  it('/slice/ can slice with multiple prefix', function (done) {
    var app = self.app;
    request(app)
      .get('/slice/entries/dateString/sgv/2014-07-{17..20}.json?count=20')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(20);
        done( );
      });
  });

  it('/slice/ can slice time with prefix and no results', function (done) {
    var app = self.app;
    request(app)
      .get('/slice/entries/dateString/sgv/1999-07.json?count=20&find[sgv][$lte]=401')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(0);
        done( );
      });
  });

  it('/times/ can get modal times', function (done) {
    var app = self.app;
    request(app)
      .get('/times/2014-07-/{0..30}T.json?')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(10);
        done( );
      });
  });

  it('/times/ can get modal minutes and times', function (done) {
    var app = self.app;
    request(app)
      .get('/times/20{14..15}-07/T{09..10}.json?')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(10);
        done( );
      });
  });
  it('/times/ can get multiple prefixen and modal minutes and times', function (done) {
    var app = self.app;
    request(app)
      .get('/times/20{14..15}/T.*:{00..60}.json?')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(10);
        done( );
      });
  });

  it('/entries/current.json', function (done) {
    request(self.app)
      .get('/entries/current.json')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(1);
        res.body[0].sgv.should.equal(100);
        done();
      });
  });

  it('/entries/:id', function (done) {
    var app = self.app;
    self.archive.list({count: 1}, function(err, records) {
      var currentId = records.pop()._id.toString();
      request(app)
        .get('/entries/'+currentId+'.json')
        .expect(200)
        .end(function (err, res) {
          res.body.should.be.instanceof(Array).and.have.lengthOf(1);
          res.body[0]._id.should.equal(currentId);
          done( );
        });
      });
    });

  it('/entries/:model', function (done) {
    var app = self.app;
    request(app)
      .get('/entries/sgv/.json?count=10&find[dateString][$gte]=2014')
      .expect(200)
      .end(function (err, res) {
        res.body.should.be.instanceof(Array).and.have.lengthOf(10);
        done( );
      });
  });

  it('disallow POST by readable /entries/preview', function (done) {
    request(self.app)
      .post('/entries/preview.json')
      .send(load('json'))
      .expect(401)
      .end(function (err, res) {
        // res.body.should.be.instanceof(Array).and.have.lengthOf(30);
        done();
      });
  });

  it('disallow deletes unauthorized', function (done) {
    var app = self.app;

    request(app)
      .delete('/entries/sgv?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20')
      .expect(401)
      .end(function (err) {
        if (err) {
          done(err);
        } else {
          request(app)
            .get('/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20')
            .expect(200)
            .end(function (err, res) {
              res.body.should.be.instanceof(Array).and.have.lengthOf(10);
              done();
            });
        }
      });
  });

  it('post an entry, query, delete, verify gone', function (done) {
    // insert a glucose entry - needs to be unique from example data
    console.log('Inserting glucose entry')
    request(self.app)
      .post('/entries/')
      .set('api-secret', self.env.api_secret || '')
      .send({
        "type": "sgv", "sgv": "199", "dateString": "2014-07-20T00:44:15.000-07:00"
        , "date": 1405791855000, "device": "dexcom", "direction": "NOT COMPUTABLE"
      })
      .expect(200)
      .end(function (err) {
        if (err) {
          done(err);
        } else {
          // make sure treatment was inserted successfully
          console.log('Ensuring glucose entry was inserted successfully');
          request(self.app)
            .get('/entries.json?find[dateString][$gte]=2014-07-20&count=100')
            .set('api-secret', self.env.api_secret || '')
            .expect(200)
            .expect(function (response) {
              response.body[0].sgv.should.equal('199');
            })
            .end(function (err) {
              if (err) {
                done(err);
              } else {
                // delete the glucose entry
                console.log('Deleting test glucose entry');
                request(self.app)
                  .delete('/entries.json?find[dateString][$gte]=2014-07-20&count=100')
                  .set('api-secret', self.env.api_secret || '')
                  .expect(200)
                  .end(function (err) {
                    if (err) {
                      done(err);
                    } else {
                      // make sure it was deleted
                      console.log('Testing if glucose entry was deleted');
                      request(self.app)
                        .get('/entries.json?find[dateString][$gte]=2014-07-20&count=100')
                        .set('api-secret', self.env.api_secret || '')
                        .expect(200)
                        .expect(function (response) {
                          response.body.length.should.equal(0);
                        })
                        .end(done);
                    }
                  });
              }
            });
        }
      });
  });

});
<|endoftext|>


<commit_before>var request = require('superagent');
var async = require('async');
var debug = require('debug')('dgv-api');

var rootUrl = process.env.DATAGOUV_URL + '/api/1';

function handleAuthorizedRequest(req, accessToken, done) {
    req.set('Authorization', 'Bearer ' + accessToken).end(function (err, resp) {
        debug('%s %s %d', resp.req.method, resp.req.path, resp.status);
        if (err) return done(err);
        if (resp.status === 401) return done(new Error('Unauthorized'));
        if (resp.error) {
            debug('Returned status %d with body:', resp.status);
            debug(resp.body);
            return done(new Error('dgv: Unexpected status'));
        }
        if (!resp.body) {
            return done(new Error('dgv: No body'));
        }
        done(null, resp.body);
    });
}

function handleBasicRequest(req, done) {
    req.end(function (err, resp) {
        debug('%s %s %d', resp.req.method, resp.req.path, resp.status);
        if (err) return done(err);
        if (resp.error || !resp.body) {
            debug(resp.body);
            return done(new Error('dgv: Unexpected result'));
        }
        done(null, resp.body);
    });
}

exports.getProfile = function (accessToken, done) {
    handleAuthorizedRequest(
        request.get(rootUrl + '/me/'),
        accessToken,
        done
    );
};

exports.getOrganization = function (id, done) {
    handleBasicRequest(
        request.get(rootUrl + '/organizations/' + id),
        done
    );
};

exports.createDataset = function (dataset, accessToken, done) {
    var datasetId;
    async.series({
        creation: function (stepDone) {
            exports.createDatasetOnly(dataset, accessToken, function (err, datasetCreated) {
                if (err) return stepDone(err);
                datasetId = datasetCreated.id;
                stepDone(null, datasetCreated);
            });
        },
        resources: function (stepDone) {
            exports.updateDatasetResources(datasetId, dataset.resources, accessToken, stepDone);
        },
        completeDataset: function (stepDone) {
            exports.getDataset(datasetId, accessToken, stepDone);
        }
    }, function (err, result) {
        if (err) return done(err);
        done(null, result.completeDataset);
    });
};

exports.createDatasetOnly = function (dataset, accessToken, done) {
    handleAuthorizedRequest(
        request.post(rootUrl + '/datasets/').send(dataset),
        accessToken,
        done
    );
};

exports.addDatasetResources = function (datasetId, resources, accessToken, done) {
    async.eachSeries(resources, function (resource, stepDone) {
        exports.createDatasetResource(datasetId, resource, accessToken, stepDone);
    }, done);
};

exports.updateDataset = function (id, dataset, accessToken, done) {
    async.series({
        resources: function (stepDone) {
            exports.updateDatasetResources(id, dataset.resources, accessToken, stepDone);
        },
        dataset: function (stepDone) {
            exports.updateDatasetOnly(id, dataset, accessToken, stepDone);
        }
    }, function (err, result) {
        if (err) return done(err);
        done(null, result.dataset);
    });
};

exports.updateDatasetOnly = function (id, dataset, accessToken, done) {
    handleAuthorizedRequest(
        request.put(rootUrl + '/datasets/' + id + '/').send(dataset),
        accessToken,
        done
    );
};

exports.createDatasetResource = function (datasetId, resource, accessToken, done) {
    handleAuthorizedRequest(
        request.post(rootUrl + '/datasets/' + datasetId + '/resources/').send(resource),
        accessToken,
        done
    );
};

exports.deleteDatasetResource = function (datasetId, resourceId, accessToken, done) {
    handleAuthorizedRequest(
        request.del(rootUrl + '/datasets/' + datasetId + '/resources/' + resourceId + '/').set('content-length', 0),
        accessToken,
        done
    );
};

exports.getDataset = function (datasetId, accessToken, done) {
    handleAuthorizedRequest(
        request.get(rootUrl + '/datasets/' + datasetId + '/'),
        accessToken,
        done
    );
};

exports.getDatasetResources = function (id, accessToken, done) {
    exports.getDataset(id, accessToken, function (err, dataset) {
        if (err) return done(err);
        done(null, dataset.resources);
    });
};

exports.cleanDatasetResources = function (id, accessToken, done) {
    exports.getDatasetResources(id, accessToken, function (err, resources) {
        async.eachSeries(resources, function (resource, stepDone) {
            exports.deleteDatasetResource(id, resource.id, accessToken, stepDone);
        }, done);
    });
};

exports.updateDatasetResources = function (id, resources, accessToken, done) {
    async.series([
        function (stepDone) {
            exports.cleanDatasetResources(id, accessToken, stepDone);
        },
        function (stepDone) {
            exports.addDatasetResources(id, resources, accessToken, stepDone);
        }
    ], done);
};

exports.deleteDataset = function (id, accessToken, done) {
    handleAuthorizedRequest(
        request.del(rootUrl + '/datasets/' + id + '/').set('content-length', 0),
        accessToken,
        done
    );
};<commit_msg>Properly migrate dgv-api to superagent 1.x<commit_after>var request = require('superagent');
var async = require('async');
var debug = require('debug')('dgv-api');

var rootUrl = process.env.DATAGOUV_URL + '/api/1';

function handleAuthorizedRequest(req, accessToken, done) {
    req.set('Authorization', 'Bearer ' + accessToken).end(function (err, resp) {
        debug('%s %s %d', resp.req.method, resp.req.path, resp.status);
        if (err) {
            if (!err.status) return done(err);
            if (err.status === 401) return done(new Error('Unauthorized'));
            debug('Returned status %d with body:', err.status);
            debug(resp.body);
            return done(new Error('dgv: Unexpected status'));
        }
        done(null, resp.body);
    });
}

function handleBasicRequest(req, done) {
    req.end(function (err, resp) {
        debug('%s %s %d', resp.req.method, resp.req.path, resp.status);
        if (err) return done(err);
        if (resp.error || !resp.body) {
            debug(resp.body);
            return done(new Error('dgv: Unexpected result'));
        }
        done(null, resp.body);
    });
}

exports.getProfile = function (accessToken, done) {
    handleAuthorizedRequest(
        request.get(rootUrl + '/me/'),
        accessToken,
        done
    );
};

exports.getOrganization = function (id, done) {
    handleBasicRequest(
        request.get(rootUrl + '/organizations/' + id),
        done
    );
};

exports.createDataset = function (dataset, accessToken, done) {
    var datasetId;
    async.series({
        creation: function (stepDone) {
            exports.createDatasetOnly(dataset, accessToken, function (err, datasetCreated) {
                if (err) return stepDone(err);
                datasetId = datasetCreated.id;
                stepDone(null, datasetCreated);
            });
        },
        resources: function (stepDone) {
            exports.updateDatasetResources(datasetId, dataset.resources, accessToken, stepDone);
        },
        completeDataset: function (stepDone) {
            exports.getDataset(datasetId, accessToken, stepDone);
        }
    }, function (err, result) {
        if (err) return done(err);
        done(null, result.completeDataset);
    });
};

exports.createDatasetOnly = function (dataset, accessToken, done) {
    handleAuthorizedRequest(
        request.post(rootUrl + '/datasets/').send(dataset),
        accessToken,
        done
    );
};

exports.addDatasetResources = function (datasetId, resources, accessToken, done) {
    async.eachSeries(resources, function (resource, stepDone) {
        exports.createDatasetResource(datasetId, resource, accessToken, stepDone);
    }, done);
};

exports.updateDataset = function (id, dataset, accessToken, done) {
    async.series({
        resources: function (stepDone) {
            exports.updateDatasetResources(id, dataset.resources, accessToken, stepDone);
        },
        dataset: function (stepDone) {
            exports.updateDatasetOnly(id, dataset, accessToken, stepDone);
        }
    }, function (err, result) {
        if (err) return done(err);
        done(null, result.dataset);
    });
};

exports.updateDatasetOnly = function (id, dataset, accessToken, done) {
    handleAuthorizedRequest(
        request.put(rootUrl + '/datasets/' + id + '/').send(dataset),
        accessToken,
        done
    );
};

exports.createDatasetResource = function (datasetId, resource, accessToken, done) {
    handleAuthorizedRequest(
        request.post(rootUrl + '/datasets/' + datasetId + '/resources/').send(resource),
        accessToken,
        done
    );
};

exports.deleteDatasetResource = function (datasetId, resourceId, accessToken, done) {
    handleAuthorizedRequest(
        request.del(rootUrl + '/datasets/' + datasetId + '/resources/' + resourceId + '/').set('content-length', 0),
        accessToken,
        done
    );
};

exports.getDataset = function (datasetId, accessToken, done) {
    handleAuthorizedRequest(
        request.get(rootUrl + '/datasets/' + datasetId + '/'),
        accessToken,
        done
    );
};

exports.getDatasetResources = function (id, accessToken, done) {
    exports.getDataset(id, accessToken, function (err, dataset) {
        if (err) return done(err);
        done(null, dataset.resources);
    });
};

exports.cleanDatasetResources = function (id, accessToken, done) {
    exports.getDatasetResources(id, accessToken, function (err, resources) {
        async.eachSeries(resources, function (resource, stepDone) {
            exports.deleteDatasetResource(id, resource.id, accessToken, stepDone);
        }, done);
    });
};

exports.updateDatasetResources = function (id, resources, accessToken, done) {
    async.series([
        function (stepDone) {
            exports.cleanDatasetResources(id, accessToken, stepDone);
        },
        function (stepDone) {
            exports.addDatasetResources(id, resources, accessToken, stepDone);
        }
    ], done);
};

exports.deleteDataset = function (id, accessToken, done) {
    handleAuthorizedRequest(
        request.del(rootUrl + '/datasets/' + id + '/').set('content-length', 0),
        accessToken,
        done
    );
};<|endoftext|>


<commit_before>import Delta from 'rich-text/lib/delta';
import DeltaOp from 'rich-text/lib/op';
import Emitter from './emitter';
import Parchment from 'parchment';
import CodeBlock from '../formats/code';
import Block, { bubbleFormats } from '../blots/block';
import clone from 'clone';
import equal from 'deep-equal';
import extend from 'extend';

class Editor {
  constructor(scroll, emitter) {
    this.scroll = scroll;
    this.emitter = emitter;
    this.emitter.on(Emitter.events.SCROLL_UPDATE, this.update.bind(this, null));
    this.delta = this.getDelta();
    this.enable();
  }

  applyDelta(delta, source = Emitter.sources.API) {
    this.updating = true;
    let consumeNextNewline = false;
    delta.ops.reduce((index, op) => {
      if (typeof op.delete === 'number') {
        this.scroll.deleteAt(index, op.delete);
        return index;
      }
      let length = op.retain || op.insert.length || 1;
      let attributes = handleOldList(op.attributes || {});
      if (op.insert != null) {
        [op, attributes] = handleOldEmbed(op, attributes);
        if (typeof op.insert === 'string') {
          let text = op.insert.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
          length = text.length;
          if (text.endsWith('\n') && consumeNextNewline) {
            consumeNextNewline = false;
            text = text.slice(0, -1);
          }
          if (index >= this.scroll.length() && !text.endsWith('\n')) {
            consumeNextNewline = true;
          }
          this.scroll.insertAt(index, text);
          let [line, offset] = this.scroll.line(index);
          let formats = extend({}, bubbleFormats(line));
          if (line instanceof Block) {
            let [leaf, ] = line.descendant(Parchment.Leaf, offset);
            formats = extend(formats, bubbleFormats(leaf));
          }
          attributes = DeltaOp.attributes.diff(formats, attributes) || {};
        } else if (typeof op.insert === 'object') {
          let key = Object.keys(op.insert)[0];  // There should only be one key
          if (key != null) {
            this.scroll.insertAt(index, key, op.insert[key]);
          } else {
            return index;
          }
        }
      }
      Object.keys(attributes).forEach((name) => {
        this.scroll.formatAt(index, length, name, attributes[name]);
      });
      return index + length;
    }, 0);
    this.updating = false;
    return this.update(delta, source);
  }

  deleteText(index, length, source = Emitter.sources.API) {
    this.scroll.deleteAt(index, length);
    return this.update(new Delta().retain(index).delete(length), source);
  }

  enable(enabled = true) {
    this.scroll.domNode.setAttribute('contenteditable', enabled);
  }

  formatLine(index, length, formats = {}, source = Emitter.sources.API) {
    this.scroll.update();
    Object.keys(formats).forEach((format) => {
      let lines = this.scroll.lines(index, Math.max(length, 1));
      let lengthRemaining = length;
      lines.forEach((line, i) => {
        let lineLength = line.length();
        if (!(line instanceof CodeBlock)) {
          line.format(format, formats[format]);
        } else {
          let codeIndex = index - line.offset(this.scroll);
          let codeLength = line.newlineIndex(codeIndex + lengthRemaining) - codeIndex + 1;
          line.formatAt(codeIndex, codeLength, format, formats[format]);
        }
        lengthRemaining -= lineLength;
      });
    });
    this.scroll.optimize();
    return this.update(new Delta().retain(index).retain(length, clone(formats)), source);
  }

  formatText(index, length, formats = {}, source = Emitter.sources.API) {
    Object.keys(formats).forEach((format) => {
      this.scroll.formatAt(index, length, format, formats[format]);
    });
    return this.update(new Delta().retain(index).retain(length, clone(formats)), source);
  }

  getContents(index, length) {
    return this.delta.slice(index, index + length);
  }

  getDelta() {
    return this.scroll.lines().reduce((delta, line) => {
      return delta.concat(line.delta());
    }, new Delta());
  }

  getFormat(index, length = 0) {
    let lines = [], leaves = [];
    if (length === 0) {
      this.scroll.path(index).forEach(function(path) {
        let [blot, offset] = path;
        if (blot instanceof Block) {
          lines.push(blot);
        } else if (blot instanceof Parchment.Leaf) {
          leaves.push(blot);
        }
      });
    } else {
      lines = this.scroll.lines(index, length);
      leaves = this.scroll.descendants(Parchment.Leaf, index, length);
    }
    let formatsArr = [lines, leaves].map(function(blots) {
      if (blots.length === 0) return {};
      let formats = bubbleFormats(blots.shift());
      while (Object.keys(formats).length > 0) {
        let blot = blots.shift();
        if (blot == null) return formats;
        formats = combineFormats(bubbleFormats(blot), formats);
      }
      return formats;
    });
    return extend.apply(extend, formatsArr);
  }

  getText(index, length) {
    return this.getContents(index, length).ops.map(function(op) {
      return (typeof op.insert === 'string') ? op.insert : '';
    }).join('');
  }

  insertEmbed(index, embed, value, source = Emitter.sources.API) {
    this.scroll.insertAt(index, embed, value);
    return this.update(new Delta().retain(index).insert({ [embed]: value }), source);
  }

  insertText(index, text, formats = {}, source = Emitter.sources.API) {
    text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
    this.scroll.insertAt(index, text);
    Object.keys(formats).forEach((format) => {
      this.scroll.formatAt(index, text.length, format, formats[format]);
    });
    return this.update(new Delta().retain(index).insert(text, clone(formats)), source)
  }

  isBlank() {
    if (this.scroll.children.length == 0) return true;
    if (this.scroll.children.length > 1) return false;
    let child = this.scroll.children.head;
    return child.length() <= 1 && Object.keys(child.formats()).length == 0;
  }

  removeFormat(index, length, source) {
    let text = this.getText(index, length);
    let [line, offset] = this.scroll.line(index + length);
    let suffixLength = 0, suffix = new Delta();
    if (line != null) {
      if (!(line instanceof CodeBlock)) {
        suffixLength = line.length() - offset;
      } else {
        suffixLength = line.newlineIndex(offset) - offset + 1;
      }
      suffix = line.delta().slice(offset, offset + suffixLength - 1).insert('\n');
    }
    let contents = this.getContents(index, length + suffixLength);
    let diff = contents.diff(new Delta().insert(text).concat(suffix));
    let delta = new Delta().retain(index).concat(diff);
    return this.applyDelta(delta, source);
  }

  update(change, source = Emitter.sources.USER) {
    if (this.updating) return;
    let oldDelta = this.delta;
    this.delta = this.getDelta();
    if (!change || !equal(oldDelta.compose(change), this.delta)) {
      change = oldDelta.diff(this.delta);
    }
    if (change.length() > 0) {
      let args = [Emitter.events.TEXT_CHANGE, change, oldDelta, source];
      this.emitter.emit(Emitter.events.EDITOR_CHANGE, ...args);
      if (source !== Emitter.sources.SILENT) {
        this.emitter.emit(...args);
      }
    }
    return change;
  }
}

function combineFormats(formats, combined) {
  return Object.keys(combined).reduce(function(merged, name) {
    if (formats[name] == null) return merged;
    if (combined[name] === formats[name]) {
      merged[name] = combined[name];
    } else if (Array.isArray(combined[name])) {
      if (combined[name].indexOf(formats[name]) < 0) {
        merged[name] = combined[name].concat([formats[name]]);
      }
    } else {
      merged[name] = [combined[name], formats[name]];
    }
    return merged;
  }, {});
}

function handleOldEmbed(op, attributes) {
  if (op.insert === 1) {
    attributes = clone(attributes);
    op = {
      insert: { image: attributes.image },
      attributes: attributes
    };
    delete attributes['image'];
  }
  return [op, attributes];
}

function handleOldList(attributes) {
  if (attributes['list'] === true) {
    attributes = clone(attributes);
    attributes['list'] = 'ordered';
  } else if (attributes['bullet'] === true) {
    attributes = clone(attributes);
    attributes['list'] = 'bullet';
  }
  return attributes;
}

export default Editor;
<commit_msg>keep track of scroll length<commit_after>import Delta from 'rich-text/lib/delta';
import DeltaOp from 'rich-text/lib/op';
import Emitter from './emitter';
import Parchment from 'parchment';
import CodeBlock from '../formats/code';
import Block, { bubbleFormats } from '../blots/block';
import clone from 'clone';
import equal from 'deep-equal';
import extend from 'extend';

class Editor {
  constructor(scroll, emitter) {
    this.scroll = scroll;
    this.emitter = emitter;
    this.emitter.on(Emitter.events.SCROLL_UPDATE, this.update.bind(this, null));
    this.delta = this.getDelta();
    this.enable();
  }

  applyDelta(delta, source = Emitter.sources.API) {
    this.updating = true;
    let consumeNextNewline = false;
    let scrollLength = this.scroll.length();
    delta.ops.reduce((index, op) => {
      if (typeof op.delete === 'number') {
        this.scroll.deleteAt(index, op.delete);
        scrollLength -= op.delete;
        return index;
      }
      let length = op.retain || op.insert.length || 1;
      let attributes = handleOldList(op.attributes || {});
      if (op.insert != null) {
        [op, attributes] = handleOldEmbed(op, attributes);
        if (typeof op.insert === 'string') {
          let text = op.insert.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
          length = text.length;
          if (text.endsWith('\n') && consumeNextNewline) {
            consumeNextNewline = false;
            text = text.slice(0, -1);
          }
          if (index >= scrollLength && !text.endsWith('\n')) {
            consumeNextNewline = true;
          }
          this.scroll.insertAt(index, text);
          let [line, offset] = this.scroll.line(index);
          let formats = extend({}, bubbleFormats(line));
          if (line instanceof Block) {
            let [leaf, ] = line.descendant(Parchment.Leaf, offset);
            formats = extend(formats, bubbleFormats(leaf));
          }
          attributes = DeltaOp.attributes.diff(formats, attributes) || {};
        } else if (typeof op.insert === 'object') {
          let key = Object.keys(op.insert)[0];  // There should only be one key
          if (key != null) {
            this.scroll.insertAt(index, key, op.insert[key]);
          } else {
            return index;
          }
        }
        scrollLength += length;
      }
      Object.keys(attributes).forEach((name) => {
        this.scroll.formatAt(index, length, name, attributes[name]);
      });
      return index + length;
    }, 0);
    this.updating = false;
    return this.update(delta, source);
  }

  deleteText(index, length, source = Emitter.sources.API) {
    this.scroll.deleteAt(index, length);
    return this.update(new Delta().retain(index).delete(length), source);
  }

  enable(enabled = true) {
    this.scroll.domNode.setAttribute('contenteditable', enabled);
  }

  formatLine(index, length, formats = {}, source = Emitter.sources.API) {
    this.scroll.update();
    Object.keys(formats).forEach((format) => {
      let lines = this.scroll.lines(index, Math.max(length, 1));
      let lengthRemaining = length;
      lines.forEach((line, i) => {
        let lineLength = line.length();
        if (!(line instanceof CodeBlock)) {
          line.format(format, formats[format]);
        } else {
          let codeIndex = index - line.offset(this.scroll);
          let codeLength = line.newlineIndex(codeIndex + lengthRemaining) - codeIndex + 1;
          line.formatAt(codeIndex, codeLength, format, formats[format]);
        }
        lengthRemaining -= lineLength;
      });
    });
    this.scroll.optimize();
    return this.update(new Delta().retain(index).retain(length, clone(formats)), source);
  }

  formatText(index, length, formats = {}, source = Emitter.sources.API) {
    Object.keys(formats).forEach((format) => {
      this.scroll.formatAt(index, length, format, formats[format]);
    });
    return this.update(new Delta().retain(index).retain(length, clone(formats)), source);
  }

  getContents(index, length) {
    return this.delta.slice(index, index + length);
  }

  getDelta() {
    return this.scroll.lines().reduce((delta, line) => {
      return delta.concat(line.delta());
    }, new Delta());
  }

  getFormat(index, length = 0) {
    let lines = [], leaves = [];
    if (length === 0) {
      this.scroll.path(index).forEach(function(path) {
        let [blot, offset] = path;
        if (blot instanceof Block) {
          lines.push(blot);
        } else if (blot instanceof Parchment.Leaf) {
          leaves.push(blot);
        }
      });
    } else {
      lines = this.scroll.lines(index, length);
      leaves = this.scroll.descendants(Parchment.Leaf, index, length);
    }
    let formatsArr = [lines, leaves].map(function(blots) {
      if (blots.length === 0) return {};
      let formats = bubbleFormats(blots.shift());
      while (Object.keys(formats).length > 0) {
        let blot = blots.shift();
        if (blot == null) return formats;
        formats = combineFormats(bubbleFormats(blot), formats);
      }
      return formats;
    });
    return extend.apply(extend, formatsArr);
  }

  getText(index, length) {
    return this.getContents(index, length).ops.map(function(op) {
      return (typeof op.insert === 'string') ? op.insert : '';
    }).join('');
  }

  insertEmbed(index, embed, value, source = Emitter.sources.API) {
    this.scroll.insertAt(index, embed, value);
    return this.update(new Delta().retain(index).insert({ [embed]: value }), source);
  }

  insertText(index, text, formats = {}, source = Emitter.sources.API) {
    text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
    this.scroll.insertAt(index, text);
    Object.keys(formats).forEach((format) => {
      this.scroll.formatAt(index, text.length, format, formats[format]);
    });
    return this.update(new Delta().retain(index).insert(text, clone(formats)), source)
  }

  isBlank() {
    if (this.scroll.children.length == 0) return true;
    if (this.scroll.children.length > 1) return false;
    let child = this.scroll.children.head;
    return child.length() <= 1 && Object.keys(child.formats()).length == 0;
  }

  removeFormat(index, length, source) {
    let text = this.getText(index, length);
    let [line, offset] = this.scroll.line(index + length);
    let suffixLength = 0, suffix = new Delta();
    if (line != null) {
      if (!(line instanceof CodeBlock)) {
        suffixLength = line.length() - offset;
      } else {
        suffixLength = line.newlineIndex(offset) - offset + 1;
      }
      suffix = line.delta().slice(offset, offset + suffixLength - 1).insert('\n');
    }
    let contents = this.getContents(index, length + suffixLength);
    let diff = contents.diff(new Delta().insert(text).concat(suffix));
    let delta = new Delta().retain(index).concat(diff);
    return this.applyDelta(delta, source);
  }

  update(change, source = Emitter.sources.USER) {
    if (this.updating) return;
    let oldDelta = this.delta;
    this.delta = this.getDelta();
    if (!change || !equal(oldDelta.compose(change), this.delta)) {
      change = oldDelta.diff(this.delta);
    }
    if (change.length() > 0) {
      let args = [Emitter.events.TEXT_CHANGE, change, oldDelta, source];
      this.emitter.emit(Emitter.events.EDITOR_CHANGE, ...args);
      if (source !== Emitter.sources.SILENT) {
        this.emitter.emit(...args);
      }
    }
    return change;
  }
}

function combineFormats(formats, combined) {
  return Object.keys(combined).reduce(function(merged, name) {
    if (formats[name] == null) return merged;
    if (combined[name] === formats[name]) {
      merged[name] = combined[name];
    } else if (Array.isArray(combined[name])) {
      if (combined[name].indexOf(formats[name]) < 0) {
        merged[name] = combined[name].concat([formats[name]]);
      }
    } else {
      merged[name] = [combined[name], formats[name]];
    }
    return merged;
  }, {});
}

function handleOldEmbed(op, attributes) {
  if (op.insert === 1) {
    attributes = clone(attributes);
    op = {
      insert: { image: attributes.image },
      attributes: attributes
    };
    delete attributes['image'];
  }
  return [op, attributes];
}

function handleOldList(attributes) {
  if (attributes['list'] === true) {
    attributes = clone(attributes);
    attributes['list'] = 'ordered';
  } else if (attributes['bullet'] === true) {
    attributes = clone(attributes);
    attributes['list'] = 'bullet';
  }
  return attributes;
}

export default Editor;
<|endoftext|>


<commit_before>import { StyleSheet } from 'react-native';

export default StyleSheet.create({
  container: {
    backgroundColor: '#DEE0E0',
    flex: 1,
    overflow: 'hidden',
  },

  endFullScreenPreviewButton: {
    position: 'absolute',
    right: 30,
    bottom: 30,
    width: 40,
    height: 40,

    alignItems: 'center',
    backgroundColor: 'hsla(198,18%,22%,0.6)',
    borderRadius: 20,
    justifyContent: 'center',
    shadowColor: 'black',
    shadowOffset: { height: 1 },
    shadowOpacity: 0.2,
    shadowRadius: 4,
  },
  endFullScreenPreviewButtonImage: {
    width: 18,
    height: 18,
  },

  contentSection: {
    flex: 1,
    flexDirection: 'row',
  },

  previewSection: {
    alignItems: 'center',
    flex: 2,
    justifyContent: 'center',
  },
  previewElementWrapper: {
    shadowColor: 'black',
    shadowOffset: { height: 1 },
    shadowOpacity: 0.3,
    shadowRadius: 20,
  },
});
<commit_msg>Sibling removed... flex: 1<commit_after>import { StyleSheet } from 'react-native';

export default StyleSheet.create({
  container: {
    backgroundColor: '#DEE0E0',
    flex: 1,
    overflow: 'hidden',
  },

  endFullScreenPreviewButton: {
    position: 'absolute',
    right: 30,
    bottom: 30,
    width: 40,
    height: 40,

    alignItems: 'center',
    backgroundColor: 'hsla(198,18%,22%,0.6)',
    borderRadius: 20,
    justifyContent: 'center',
    shadowColor: 'black',
    shadowOffset: { height: 1 },
    shadowOpacity: 0.2,
    shadowRadius: 4,
  },
  endFullScreenPreviewButtonImage: {
    width: 18,
    height: 18,
  },

  contentSection: {
    flex: 1,
    flexDirection: 'row',
  },

  previewSection: {
    alignItems: 'center',
    flex: 1,
    justifyContent: 'center',
  },
  previewElementWrapper: {
    shadowColor: 'black',
    shadowOffset: { height: 1 },
    shadowOpacity: 0.3,
    shadowRadius: 20,
  },
});
<|endoftext|>


<commit_before>var MAPS = require('./../../maps'),
    MAP64 = MAPS.arrs[64],
    MAPASCII = MAPS.arrs['ascii'];

var EMPTY_BYTE = '00000000',
    PAD_CHAR = '=';

/**
 * Returns a character its ascii value.
 *
 * @param c {string}
 * @return  {int}     Input character its ascii value
 */
function getCharVal(c){
  return MAPASCII.indexOf(c) + 32;
}

/**
 * Gets base2 representation of the given int in byte format.
 * Prefixes with bytes of value 0.
 *
 * @param b10 {int}
 * @return {string}  Base2 byte
 */
function b10ToByte(b10){
  var b2 = '',
      pref_len;

  while(b10 != 0){
    b2 = (b10 % 2) + b2;
    b10 = Math.floor(b10 / 2);
  }

  pref_len = (4 - b2.length%4);

  for(var i=0; i<pref_len; i+=1)
    b2 = '0' + b2;

  return b2;
}

/**
 * Converts a base2 string to base10 int which will be used
 * to select a character from the base64 map.
 *
 * @param base2 {string}
 * @return      {string}  Base64 character
 */
function get64Char(b2){
  var b10 = 0,
      rev_b2_arr = b2.toString().split('').reverse();

  for(var i=0; i<rev_b2_arr.length; i+=1)
    b10 += rev_b2_arr[i] * Math.pow(2, i);

  return MAP64[b10];
}

module.exports = {

  /**
   * @param ascii {string}
   * @return b64  {string}
   */
  '64': function (ascii){
    var b64 = '',
        bit_pattern = '',
        pad_count = 3-ascii.length%3 == 3 ? 0 : 3-ascii.length%3;

    // Create bit_pattern
    for(var c_i=0; c_i<ascii.length; c_i+=1)
      bit_pattern += b10ToByte(getCharVal(ascii[c_i]));

    // Add empty bytes to bit_pattern if it contains incomplete bytes
    for(var p_i=0; p_i<pad_count; p_i+=1)
      bit_pattern += EMPTY_BYTE;

    // Create b64 string from bit_pattern, padding bytes
    // will be set to base64 'A' instead of '='
    for(var b_i=0; b_i<bit_pattern.length; b_i+=6)
      b64 += get64Char(bit_pattern.substr(b_i, 6));

    // Replace 'A' padding chars with '='
    b64 = b64.slice(0, b64.length-pad_count);
    for(var t_i=0; t_i<pad_count; t_i+=1)
      b64 += PAD_CHAR;

    return b64;
  }

};
<commit_msg>doc fix<commit_after>var MAPS = require('./../../maps'),
    MAP64 = MAPS.arrs[64],
    MAPASCII = MAPS.arrs['ascii'];

var EMPTY_BYTE = '00000000',
    PAD_CHAR = '=';

/**
 * Returns a character its ascii value.
 *
 * @param c {string}
 * @return  {int}     Input character its ascii value
 */
function getCharVal(c){
  return MAPASCII.indexOf(c) + 32;
}

/**
 * Gets base2 representation of the given int in byte format.
 * Prefixes with bytes of value 0.
 *
 * @param b10 {int}
 * @return {string}  Base2 byte
 */
function b10ToByte(b10){
  var b2 = '',
      pref_len;

  while(b10 != 0){
    b2 = (b10 % 2) + b2;
    b10 = Math.floor(b10 / 2);
  }

  pref_len = (4 - b2.length%4);

  for(var i=0; i<pref_len; i+=1)
    b2 = '0' + b2;

  return b2;
}

/**
 * Converts a base2 string to base10 int which will be used
 * to select a character from the base64 map.
 *
 * @param b2 {string}
 * @return   {string}  Base64 character
 */
function get64Char(b2){
  var b10 = 0,
      rev_b2_arr = b2.toString().split('').reverse();

  for(var i=0; i<rev_b2_arr.length; i+=1)
    b10 += rev_b2_arr[i] * Math.pow(2, i);

  return MAP64[b10];
}

module.exports = {

  /**
   * @param ascii {string}
   * @return b64  {string}
   */
  '64': function (ascii){
    var b64 = '',
        bit_pattern = '',
        pad_count = 3-ascii.length%3 == 3 ? 0 : 3-ascii.length%3;

    // Create bit_pattern
    for(var c_i=0; c_i<ascii.length; c_i+=1)
      bit_pattern += b10ToByte(getCharVal(ascii[c_i]));

    // Add empty bytes to bit_pattern if it contains incomplete bytes
    for(var p_i=0; p_i<pad_count; p_i+=1)
      bit_pattern += EMPTY_BYTE;

    // Create b64 string from bit_pattern, padding bytes
    // will be set to base64 'A' instead of '='
    for(var b_i=0; b_i<bit_pattern.length; b_i+=6)
      b64 += get64Char(bit_pattern.substr(b_i, 6));

    // Replace 'A' padding chars with '='
    b64 = b64.slice(0, b64.length-pad_count);
    for(var t_i=0; t_i<pad_count; t_i+=1)
      b64 += PAD_CHAR;

    return b64;
  }

};
<|endoftext|>


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

angular.module('zeppelinWebApp').service('saveAsService', SaveAsService)

function SaveAsService (browserDetectService) {
  'ngInject'

  this.saveAs = function (content, filename, extension) {
    let BOM = '\uFEFF'
    if (browserDetectService.detectIE()) {
      angular.element('body').append('')
      let frameSaveAs = angular.element('body > iframe#SaveAsId')[0].contentWindow
      content = BOM + content
      frameSaveAs.document.open('text/json', 'replace')
      frameSaveAs.document.write(content)
      frameSaveAs.document.close()
      frameSaveAs.focus()
      let t1 = Date.now()
      frameSaveAs.document.execCommand('SaveAs', false, filename + '.' + extension)
      let t2 = Date.now()

      // This means, this version of IE dosen't support auto download of a file with extension provided in param
      // falling back to ".txt"
      if (t1 === t2) {
        frameSaveAs.document.execCommand('SaveAs', true, filename + '.txt')
      }
      angular.element('body > iframe#SaveAsId').remove()
    } else {
      content = 'data:image/svg;charset=utf-8,' + BOM + encodeURIComponent(content)
      angular.element('body').append('')
      let saveAsElement = angular.element('body > a#SaveAsId')
      saveAsElement.attr('href', content)
      saveAsElement.attr('download', filename + '.' + extension)
      saveAsElement.attr('target', '_blank')
      saveAsElement[0].click()
      saveAsElement.remove()
    }
  }
}
<commit_msg>[ZEPPELIN-2850] URI Data scheme -> Blob creation<commit_after>/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

angular.module('zeppelinWebApp').service('saveAsService', SaveAsService)

function SaveAsService (browserDetectService) {
  'ngInject'

  this.saveAs = function (content, filename, extension) {
    let BOM = '\uFEFF'
    if (browserDetectService.detectIE()) {
      angular.element('body').append('')
      let frameSaveAs = angular.element('body > iframe#SaveAsId')[0].contentWindow
      content = BOM + content
      frameSaveAs.document.open('text/json', 'replace')
      frameSaveAs.document.write(content)
      frameSaveAs.document.close()
      frameSaveAs.focus()
      let t1 = Date.now()
      frameSaveAs.document.execCommand('SaveAs', false, filename + '.' + extension)
      let t2 = Date.now()

      // This means, this version of IE dosen't support auto download of a file with extension provided in param
      // falling back to ".txt"
      if (t1 === t2) {
        frameSaveAs.document.execCommand('SaveAs', true, filename + '.txt')
      }
      angular.element('body > iframe#SaveAsId').remove()
    } else {
      let binaryData = []
      binaryData.push(BOM)
      binaryData.push(content)
      content = window.URL.createObjectURL(new Blob(binaryData))

      angular.element('body').append('')
      let saveAsElement = angular.element('body > a#SaveAsId')
      saveAsElement.attr('href', content)
      saveAsElement.attr('download', filename + '.' + extension)
      saveAsElement.attr('target', '_blank')
      saveAsElement[0].click()
      saveAsElement.remove()
      window.URL.revokeObjectURL(content)
    }
  }
}
<|endoftext|>