<commit_before>'use strict';

// Delete anonymous level sessions 90 days or older

if (process.argv.length !== 4) {
  log("Usage: node <script> <mongo connection Url> <mongo connection Url level session>");
  log("Include ?readPreference=secondary in connection URLs");
  process.exit();
}

const mongoConnUrlRead = process.argv[2];
const mongoConnUrlLevelSessionsWRITE = process.argv[3];

const scriptStartTime = new Date();
const co = require('co');
const mongoose = require('mongoose');
const MongoClient = require('mongodb').MongoClient;
const ObjectID = require('mongodb').ObjectID;
const Promise = require('bluebird');
const moment = require('moment');
const _ = require('lodash');

const debugOutput = true;

const dayRange = 10;

const startDate = new Date('2017-08-15'); // No data before this point due to previous anonymous clean up
const newestDate = new Date();
newestDate.setUTCDate(newestDate.getUTCDate() - 90);
debug(`Cutoff date for old anonymous users = ${newestDate.toISOString()}`);

co(function*() {
  const mainDb = yield MongoClient.connect(mongoConnUrlRead, {connectTimeoutMS: 1000 * 60 * 60, socketTimeoutMS: 1000 * 60 * 60});
  const lsDb = yield MongoClient.connect(mongoConnUrlLevelSessionsWRITE, {connectTimeoutMS: 1000 * 60 * 60, socketTimeoutMS: 1000 * 60 * 60});

  let totalUsers = 0;
  let totalLevelSessionsDeleted = 0;

  const currentDate = new Date(startDate);
  while (currentDate < newestDate) {
    let endDate = new Date(currentDate);
    endDate.setUTCDate(endDate.getUTCDate() + dayRange);
    if (endDate > newestDate) endDate = newestDate;
    debug(`*** Processing ${currentDate.toISOString().substring(0, 10)} to ${endDate.toISOString().substring(0, 10)} ***`);

    // Find anonymous users
    const query = {$and: [{anonymous: true}, {_id: {$gte: ObjectID(objectIdWithTimestamp(currentDate))}}, {_id: {$lt: ObjectID(objectIdWithTimestamp(endDate))}}]};
    const users = yield mainDb.collection('users').find(query, {'activity.login.last': 1, dateCreated: 1}).toArray();
    debug(`${users.length} old anonymous users found.`);
    // console.log(users);
    const allUserIdStrings = users.map((u) => u._id.toString());

    totalUsers += users.length;

    const userBatchSize = 10000;
    for (let i = 0; i * userBatchSize < allUserIdStrings.length; i++) {
      debug(`* User Batch ${i + 1} of ${Math.ceil(allUserIdStrings.length / userBatchSize)} for ${currentDate.toISOString().substring(0, 10)} to ${endDate.toISOString().substring(0, 10)} *`);
      const start = i * userBatchSize;
      const end = Math.min(i * userBatchSize + userBatchSize - 1, allUserIdStrings.length - 1);
      const userIdStrings = allUserIdStrings.slice(start, end);

      // Delete level sessions
      // const levelSessions = yield lsDb.collection('level.sessions').find({$and: [{creator: {$in: userIdStrings}}, {_id: {$gte: ObjectID(objectIdWithTimestamp(currentDate))}}, {_id: {$lt: ObjectID(objectIdWithTimestamp(endDate))}}]}, {_id: 1}).toArray();
      const response = yield lsDb.collection('level.sessions').remove({creator: {$in: userIdStrings}, _id: {$lt: ObjectID(objectIdWithTimestamp(newestDate))}});
      // debug(`${levelSessions.length} level sessions found.`);

      if (response.result.n > 0) {
        debug(`Deleted ${response.result.n}`);
      }

      totalLevelSessionsDeleted += response.result.n;

      // Don't hammer the databases constantly
      var waitTill = new Date(new Date().getTime() + 500);
      while(waitTill > new Date());
      // break;
    }

    // Don't hammer the databases constantly
    var waitTill = new Date(new Date().getTime() + 500);
    while(waitTill > new Date());

    currentDate.setUTCDate(currentDate.getUTCDate() + dayRange);
    // break;
  }

  console.log(`${totalUsers} anonymous users and ${totalLevelSessionsDeleted} level sessions deleted`);

  mainDb.close();
  lsDb.close();
  debug(`Script runtime: ${new Date() - scriptStartTime}ms`);
}).catch(function(e) {
  console.log('Error: ', e.stack)
  console.log(`Script runtime: ${new Date() - scriptStartTime}ms`);
  process.exit();
});

// * Helper functions

function objectIdWithTimestamp(timestamp) {
  // Convert string date to Date object (otherwise assume timestamp is a date)
  if (typeof(timestamp) == 'string') timestamp = new Date(timestamp);
  // Convert date object to hex seconds since Unix epoch
  var hexSeconds = Math.floor(timestamp/1000).toString(16);
  // Create an ObjectId with that hex timestamp
  var constructedObjectId = mongoose.Types.ObjectId(hexSeconds + "0000000000000000");
  return constructedObjectId;
}

function debug(msg) {
  if (debugOutput) console.log(`${new Date().toISOString()} ${msg}`);
}

function getMedian(values) {
  if (values.length === 0) return -1;
  values.sort((a, b) => a - b);
  const lowMiddle = Math.floor((values.length - 1) / 2);
  const highMiddle = Math.ceil((values.length - 1) / 2);
  return (values[lowMiddle] + values[highMiddle]) / 2;
}
<commit_msg>Update delete anon last clean up point<commit_after>'use strict';

// Delete anonymous level sessions 90 days or older

if (process.argv.length !== 4) {
  log("Usage: node <script> <mongo connection Url> <mongo connection Url level session>");
  log("Include ?readPreference=secondary in connection URLs");
  process.exit();
}

const mongoConnUrlRead = process.argv[2];
const mongoConnUrlLevelSessionsWRITE = process.argv[3];

const scriptStartTime = new Date();
const co = require('co');
const mongoose = require('mongoose');
const MongoClient = require('mongodb').MongoClient;
const ObjectID = require('mongodb').ObjectID;
const Promise = require('bluebird');
const moment = require('moment');
const _ = require('lodash');

const debugOutput = true;

const dayRange = 10;

const startDate = new Date('2017-11-01'); // No data before this point due to previous anonymous clean up
const newestDate = new Date();
newestDate.setUTCDate(newestDate.getUTCDate() - 90);
debug(`Cutoff date for old anonymous users = ${newestDate.toISOString()}`);

co(function*() {
  const mainDb = yield MongoClient.connect(mongoConnUrlRead, {connectTimeoutMS: 1000 * 60 * 60, socketTimeoutMS: 1000 * 60 * 60});
  const lsDb = yield MongoClient.connect(mongoConnUrlLevelSessionsWRITE, {connectTimeoutMS: 1000 * 60 * 60, socketTimeoutMS: 1000 * 60 * 60});

  let totalUsers = 0;
  let totalLevelSessionsDeleted = 0;

  const currentDate = new Date(startDate);
  while (currentDate < newestDate) {
    let endDate = new Date(currentDate);
    endDate.setUTCDate(endDate.getUTCDate() + dayRange);
    if (endDate > newestDate) endDate = newestDate;
    debug(`*** Processing ${currentDate.toISOString().substring(0, 10)} to ${endDate.toISOString().substring(0, 10)} ***`);

    // Find anonymous users
    const query = {$and: [{anonymous: true}, {_id: {$gte: ObjectID(objectIdWithTimestamp(currentDate))}}, {_id: {$lt: ObjectID(objectIdWithTimestamp(endDate))}}]};
    const users = yield mainDb.collection('users').find(query, {'activity.login.last': 1, dateCreated: 1}).toArray();
    debug(`${users.length} old anonymous users found.`);
    // console.log(users);
    const allUserIdStrings = users.map((u) => u._id.toString());

    totalUsers += users.length;

    const userBatchSize = 10000;
    for (let i = 0; i * userBatchSize < allUserIdStrings.length; i++) {
      debug(`* User Batch ${i + 1} of ${Math.ceil(allUserIdStrings.length / userBatchSize)} for ${currentDate.toISOString().substring(0, 10)} to ${endDate.toISOString().substring(0, 10)} *`);
      const start = i * userBatchSize;
      const end = Math.min(i * userBatchSize + userBatchSize - 1, allUserIdStrings.length - 1);
      const userIdStrings = allUserIdStrings.slice(start, end);

      // Delete level sessions
      // const levelSessions = yield lsDb.collection('level.sessions').find({$and: [{creator: {$in: userIdStrings}}, {_id: {$gte: ObjectID(objectIdWithTimestamp(currentDate))}}, {_id: {$lt: ObjectID(objectIdWithTimestamp(endDate))}}]}, {_id: 1}).toArray();
      const response = yield lsDb.collection('level.sessions').remove({creator: {$in: userIdStrings}, _id: {$lt: ObjectID(objectIdWithTimestamp(newestDate))}});
      // debug(`${levelSessions.length} level sessions found.`);

      if (response.result.n > 0) {
        debug(`Deleted ${response.result.n}`);
      }

      totalLevelSessionsDeleted += response.result.n;

      // Don't hammer the databases constantly
      var waitTill = new Date(new Date().getTime() + 500);
      while(waitTill > new Date());
      // break;
    }

    // Don't hammer the databases constantly
    var waitTill = new Date(new Date().getTime() + 500);
    while(waitTill > new Date());

    currentDate.setUTCDate(currentDate.getUTCDate() + dayRange);
    // break;
  }

  console.log(`${totalUsers} anonymous users and ${totalLevelSessionsDeleted} level sessions deleted`);

  mainDb.close();
  lsDb.close();
  debug(`Script runtime: ${new Date() - scriptStartTime}ms`);
}).catch(function(e) {
  console.log('Error: ', e.stack)
  console.log(`Script runtime: ${new Date() - scriptStartTime}ms`);
  process.exit();
});

// * Helper functions

function objectIdWithTimestamp(timestamp) {
  // Convert string date to Date object (otherwise assume timestamp is a date)
  if (typeof(timestamp) == 'string') timestamp = new Date(timestamp);
  // Convert date object to hex seconds since Unix epoch
  var hexSeconds = Math.floor(timestamp/1000).toString(16);
  // Create an ObjectId with that hex timestamp
  var constructedObjectId = mongoose.Types.ObjectId(hexSeconds + "0000000000000000");
  return constructedObjectId;
}

function debug(msg) {
  if (debugOutput) console.log(`${new Date().toISOString()} ${msg}`);
}

function getMedian(values) {
  if (values.length === 0) return -1;
  values.sort((a, b) => a - b);
  const lowMiddle = Math.floor((values.length - 1) / 2);
  const highMiddle = Math.ceil((values.length - 1) / 2);
  return (values[lowMiddle] + values[highMiddle]) / 2;
}
<|endoftext|>


<commit_before>/**
 * Module that controls the Google Drive node settings. Includes Knockout view-model
 * for syncing data, and HGrid-folderpicker for selecting a folder.
 */
'use strict';

var ko = require('knockout');
require('knockout-punches');
var $ = require('jquery');
var m = require('mithril');
var bootbox = require('bootbox');
var Raven = require('raven-js');

var FolderPicker = require('folderpicker');
var ZeroClipboard = require('zeroclipboard');
ZeroClipboard.config('/static/vendor/bower_components/zeroclipboard/dist/ZeroClipboard.swf');
var $osf = require('osfHelpers');

ko.punches.enableAll();

/**
 * Knockout view model for the Google Drive node settings widget.
 */
var ViewModel = function(url, selector, folderPicker) {
    var self = this;
    self.url = url;
    self.loaded = false;
    self.nodeHasAuth = ko.observable(false);
    self.userHasAuth = ko.observable(false);
    // whether current user is authorizer of the addon
    self.userIsOwner = ko.observable(false);

    self.showPicker = ko.observable(false);
    self.owner = ko.observable();
    self.ownerName = ko.observable();
    self.urls = ko.observable({});
    self.loadedFolders = ko.observable(false);
    self.loading = ko.observable(false);
    self.currentFolder = ko.observable(null);
    self.currentPath = ko.observable(null);

    //Folderpicker specific
    self.folderPicker =  folderPicker;
    self.selected = ko.observable(null);
    self.showFileTypes = ko.observable(false);
    self.cancelSelection = ko.observable();
    self.loadedSettings = ko.observable(false);
    self.selectedFileTypeOption = ko.observable('');

    // Flashed messages
    self.message = ko.observable('');
    self.messageClass = ko.observable('text-info');

    // Get data from the config GET endpoint
    function onFetchSuccess(response) {
        // Update view model
        self.nodeHasAuth(response.result.nodeHasAuth);
        self.userHasAuth(response.result.userHasAuth);
        self.userIsOwner(response.result.userIsOwner);
        self.urls(response.result.urls);
        self.ownerName(response.result.ownerName);
        self.owner(response.result.urls.owner);
        self.currentPath(response.result.currentPath);
        self.currentFolder(response.result.currentFolder ?
            decodeURIComponent(response.result.currentFolder):undefined);

        self.loadedSettings(true);
    }

    function onFetchError(xhr, textstatus, error) {
        self.message('Could not fetch settings.');
    }

    function fetch() {
        $.ajax({
            url: self.url,
            type: 'GET',
            dataType: 'json'
        })
        .done(onFetchSuccess)
        .fail(onFetchError);
    }

    fetch();

    /* Change the flashed status message */
    self.changeMessage = function(text, css, timeout) {
        self.message(text);
        var cssClass = css || 'text-info';
        self.messageClass(cssClass);
        if (timeout) {
            // Reset message after timeout period
            setTimeout(function() {
                self.message('');
                self.messageClass('text-info');
            }, timeout);
        }
    };

    /** Whether or not to show the Create Access Token button */
    self.showTokenCreateButton = ko.computed(function() {
        // Invoke the observables to ensure dependency tracking
        var userHasAuth = self.userHasAuth();
        var nodeHasAuth = self.nodeHasAuth();
        var loaded = self.loadedSettings();
        return !userHasAuth && !nodeHasAuth && loaded;
    });

    self.createAuth = function() {
        return $.osf.postJSON(
            self.urls().create
        ).success(function(response){
            window.location.href = response.url;
        }).fail(function(xhr, textStatus, error) {
            self.changeMessage('Could not authorize Google Drive due to an error. Please try again later.',
                               'text-danger');
            Raven.captureMessage('Could not authorize Google Drive.', {
                url: self.urls().create,
                textStatus: textStatus,
                error: error
            });
        });
    };

    /**
     * Whether or not to show the Import Access Token Button
     */
    self.showImport = ko.computed(function() {
        // Invoke the observables to ensure dependency tracking
        var userHasAuth = self.userHasAuth();
        var nodeHasAuth = self.nodeHasAuth();
        var loaded = self.loadedSettings();
        return userHasAuth && !nodeHasAuth && loaded;
    });

    // Callback for when PUT request to import user access token
    function onImportSuccess(response) {
        var msg = response.message || 'Successfully imported access token from profile.';
        onFetchSuccess(response);
        self.changeFolder();
        self.changeMessage(msg, 'text-success', 5000);
    }

    function onImportError() {
        self.message('Error occurred while importing access token.');
        self.messageClass('text-danger');
    }

    /**
     * Send PUT request to import access token from user profile.
     */
    self.importAuth = function() {
        bootbox.confirm({
            title: 'Import Google Drive Access Token?',
            message: 'Are you sure you want to authorize this project with your Google Drive access token?',
            callback: function(confirmed) {
                if (confirmed) {
                    return $.osf.putJSON(self.urls().importAuth, {})
                        .done(onImportSuccess)
                        .fail(onImportError);
               }
            }
        });
    };

    /**
     * Send DELETE request to deauthorize this node.
     */
    function sendDeauth() {
        return $.ajax({
            url: self.urls().deauthorize,
            type: 'DELETE'
        }).done(function () {
            // Update observables
            self.nodeHasAuth(false);
            self.changeMessage('Deauthorized Google Drive.', 'text-warning', 3000);
        }).fail(function (xhr, textStatus, error) {
            self.changeMessage('Could not deauthorize Google Drive due to an error. Please try again later.', 'text-danger');
            Raven.captureMessage('Could not deauthorize Google Drive.', {
                url: self.urls().deauthorize,
                textStatus: textStatus,
                error: error
            });
        });
    }

    /** Pop up a confirmation to deauthorize Google Drive from this node.
     *  Send DELETE request if confirmed.
     */
    self.deauthorize = function() {
        bootbox.confirm({
            title: 'Deauthorize Google Drive?',
            message: 'Are you sure you want to remove this Google Drive authorization?',
            callback: function(confirmed) {
                if (confirmed) {
                    return sendDeauth();
                }
            }
        });
    };

    /** Callback for chooseFolder action.
     *   Just changes the ViewModel's self.selected observable to the selected
     *   folder.
     */
    function onPickFolder(evt, item) {
        evt.preventDefault();
        self.selected({
            id: item.data.id,
            name: '/' + (item.data.path === '/' ? ' (Full Google Drive)' : item.data.path),
            path: item.data.path
        });

        return false; // Prevent event propagation
    }

    /** Calling Google Drive API & arranging data as
     * required for treebeard Hgrid
     */
    self.changeFolder = function() {
        self.showPicker(!self.showPicker());

        if (!self.loaded) {
            self.loaded = true;
            $(self.folderPicker).folderpicker({
                onPickFolder: onPickFolder,
                filesData: self.urls().get_folders,
                initialFolderPath : self.currentPath(),
                // Lazy-load each folder's contents
                // Each row stores its url for fetching the folders it contains

                resolveLazyloadUrl : function(item){
                    return item.data.urls.get_folders;
                },
                ajaxOptions: {
                    error: function (xhr, textStatus, error) {
                        self.loading(false);
                        self.changeMessage(
                            'Could not connect to Google Drive at this time. ' +
                            'Please try again later.', 'text-warning'
                        );
                        Raven.captureMessage('Could not GET get Google Drive contents.', {
                            textStatus: textStatus,
                            error: error
                        });
                    }
                },
                folderPickerOnload: function () {},
                resolveRows: function(item) {
                    item.css = '';
                    return [
                        {
                            data : 'name',  // Data field name
                            folderIcons : true,
                            filter : false,
                            custom : function(item, col) {
                                return m('span', decodeURIComponent(item.data.name));
                            }
                        },
                        {
                            css : 'p-l-xs',
                            sortInclude : false,
                            custom : FolderPicker.selectView
                        }
                    ];
                }
            });
        }
    };

    self.showFolders = ko.computed(function(){
        return self.nodeHasAuth() && self.userIsOwner();
    });

    self.cancelSelection = function() {
        self.selected(null);
    };

    self.selectedFolderName = ko.computed(function() {
        var userIsOwner = self.userIsOwner();
        var selected = self.selected();
        return (userIsOwner && selected) ? decodeURIComponent(selected.name) : '';
    });

    function onSubmitSuccess(response) {
        self.currentFolder(decodeURIComponent(self.selected().name));
        self.changeMessage(
            'Successfully linked "' +
           $osf.htmlEscape(decodeURIComponent(self.selected().name)) +
            '". Go to the Files page to view your files.',
        'text-success', 5000);
        // Update folder in ViewModel
        self.urls(response.result.urls);
    }

    function onSubmitError() {
        self.changeMessage('Could not change settings. Please try again later.', 'text-danger');
    }

    /**
     * Send a PUT request to change the linked Google Drive folder.
     */
    self.submitSettings = function() {
        $osf.putJSON(self.urls().config, ko.toJS(self))
            .done(onSubmitSuccess)
            .fail(onSubmitError);
    };
};

function GoogleDriveNodeConfig(selector, url, folderPicker) {
    // Initialization code
    var self = this;
    self.viewModel = new ViewModel(url, selector, folderPicker);
    $.osf.applyBindings(self.viewModel, selector);
}

module.exports = GoogleDriveNodeConfig;
<commit_msg>Reset selection after submitting folderpicker in GDrive<commit_after>/**
 * Module that controls the Google Drive node settings. Includes Knockout view-model
 * for syncing data, and HGrid-folderpicker for selecting a folder.
 */
'use strict';

var ko = require('knockout');
require('knockout-punches');
var $ = require('jquery');
var m = require('mithril');
var bootbox = require('bootbox');
var Raven = require('raven-js');

var FolderPicker = require('folderpicker');
var ZeroClipboard = require('zeroclipboard');
ZeroClipboard.config('/static/vendor/bower_components/zeroclipboard/dist/ZeroClipboard.swf');
var $osf = require('osfHelpers');

ko.punches.enableAll();

/**
 * Knockout view model for the Google Drive node settings widget.
 */
var ViewModel = function(url, selector, folderPicker) {
    var self = this;
    self.url = url;
    self.loaded = false;
    self.nodeHasAuth = ko.observable(false);
    self.userHasAuth = ko.observable(false);
    // whether current user is authorizer of the addon
    self.userIsOwner = ko.observable(false);

    self.showPicker = ko.observable(false);
    self.owner = ko.observable();
    self.ownerName = ko.observable();
    self.urls = ko.observable({});
    self.loadedFolders = ko.observable(false);
    self.loading = ko.observable(false);
    self.currentFolder = ko.observable(null);
    self.currentPath = ko.observable(null);

    //Folderpicker specific
    self.folderPicker =  folderPicker;
    self.selected = ko.observable(null);
    self.showFileTypes = ko.observable(false);
    self.loadedSettings = ko.observable(false);
    self.selectedFileTypeOption = ko.observable('');

    // Flashed messages
    self.message = ko.observable('');
    self.messageClass = ko.observable('text-info');

    // Get data from the config GET endpoint
    function onFetchSuccess(response) {
        // Update view model
        self.nodeHasAuth(response.result.nodeHasAuth);
        self.userHasAuth(response.result.userHasAuth);
        self.userIsOwner(response.result.userIsOwner);
        self.urls(response.result.urls);
        self.ownerName(response.result.ownerName);
        self.owner(response.result.urls.owner);
        self.currentPath(response.result.currentPath);
        self.currentFolder(response.result.currentFolder ?
            decodeURIComponent(response.result.currentFolder):undefined);

        self.loadedSettings(true);
    }

    function onFetchError(xhr, textstatus, error) {
        self.message('Could not fetch settings.');
    }

    function fetch() {
        $.ajax({
            url: self.url,
            type: 'GET',
            dataType: 'json'
        })
        .done(onFetchSuccess)
        .fail(onFetchError);
    }

    fetch();

    /* Change the flashed status message */
    self.changeMessage = function(text, css, timeout) {
        self.message(text);
        var cssClass = css || 'text-info';
        self.messageClass(cssClass);
        if (timeout) {
            // Reset message after timeout period
            setTimeout(function() {
                self.message('');
                self.messageClass('text-info');
            }, timeout);
        }
    };

    /** Whether or not to show the Create Access Token button */
    self.showTokenCreateButton = ko.computed(function() {
        // Invoke the observables to ensure dependency tracking
        var userHasAuth = self.userHasAuth();
        var nodeHasAuth = self.nodeHasAuth();
        var loaded = self.loadedSettings();
        return !userHasAuth && !nodeHasAuth && loaded;
    });

    self.createAuth = function() {
        return $.osf.postJSON(
            self.urls().create
        ).success(function(response){
            window.location.href = response.url;
        }).fail(function(xhr, textStatus, error) {
            self.changeMessage('Could not authorize Google Drive due to an error. Please try again later.',
                               'text-danger');
            Raven.captureMessage('Could not authorize Google Drive.', {
                url: self.urls().create,
                textStatus: textStatus,
                error: error
            });
        });
    };

    /**
     * Whether or not to show the Import Access Token Button
     */
    self.showImport = ko.computed(function() {
        // Invoke the observables to ensure dependency tracking
        var userHasAuth = self.userHasAuth();
        var nodeHasAuth = self.nodeHasAuth();
        var loaded = self.loadedSettings();
        return userHasAuth && !nodeHasAuth && loaded;
    });

    // Callback for when PUT request to import user access token
    function onImportSuccess(response) {
        var msg = response.message || 'Successfully imported access token from profile.';
        onFetchSuccess(response);
        self.changeFolder();
        self.changeMessage(msg, 'text-success', 5000);
    }

    function onImportError() {
        self.message('Error occurred while importing access token.');
        self.messageClass('text-danger');
    }

    /**
     * Send PUT request to import access token from user profile.
     */
    self.importAuth = function() {
        bootbox.confirm({
            title: 'Import Google Drive Access Token?',
            message: 'Are you sure you want to authorize this project with your Google Drive access token?',
            callback: function(confirmed) {
                if (confirmed) {
                    return $.osf.putJSON(self.urls().importAuth, {})
                        .done(onImportSuccess)
                        .fail(onImportError);
               }
            }
        });
    };

    /**
     * Send DELETE request to deauthorize this node.
     */
    function sendDeauth() {
        return $.ajax({
            url: self.urls().deauthorize,
            type: 'DELETE'
        }).done(function () {
            // Update observables
            self.nodeHasAuth(false);
            self.changeMessage('Deauthorized Google Drive.', 'text-warning', 3000);
        }).fail(function (xhr, textStatus, error) {
            self.changeMessage('Could not deauthorize Google Drive due to an error. Please try again later.', 'text-danger');
            Raven.captureMessage('Could not deauthorize Google Drive.', {
                url: self.urls().deauthorize,
                textStatus: textStatus,
                error: error
            });
        });
    }

    /** Pop up a confirmation to deauthorize Google Drive from this node.
     *  Send DELETE request if confirmed.
     */
    self.deauthorize = function() {
        bootbox.confirm({
            title: 'Deauthorize Google Drive?',
            message: 'Are you sure you want to remove this Google Drive authorization?',
            callback: function(confirmed) {
                if (confirmed) {
                    return sendDeauth();
                }
            }
        });
    };

    /** Callback for chooseFolder action.
     *   Just changes the ViewModel's self.selected observable to the selected
     *   folder.
     */
    function onPickFolder(evt, item) {
        evt.preventDefault();
        self.selected({
            id: item.data.id,
            name: '/' + (item.data.path === '/' ? ' (Full Google Drive)' : item.data.path),
            path: item.data.path
        });

        return false; // Prevent event propagation
    }

    /** Calling Google Drive API & arranging data as
     * required for treebeard Hgrid
     */
    self.changeFolder = function() {
        self.showPicker(!self.showPicker());

        if (!self.loaded) {
            self.loaded = true;
            $(self.folderPicker).folderpicker({
                onPickFolder: onPickFolder,
                filesData: self.urls().get_folders,
                initialFolderPath : self.currentPath(),
                // Lazy-load each folder's contents
                // Each row stores its url for fetching the folders it contains

                resolveLazyloadUrl : function(item){
                    return item.data.urls.get_folders;
                },
                ajaxOptions: {
                    error: function (xhr, textStatus, error) {
                        self.loading(false);
                        self.changeMessage(
                            'Could not connect to Google Drive at this time. ' +
                            'Please try again later.', 'text-warning'
                        );
                        Raven.captureMessage('Could not GET get Google Drive contents.', {
                            textStatus: textStatus,
                            error: error
                        });
                    }
                },
                folderPickerOnload: function () {},
                resolveRows: function(item) {
                    item.css = '';
                    return [
                        {
                            data : 'name',  // Data field name
                            folderIcons : true,
                            filter : false,
                            custom : function(item, col) {
                                return m('span', decodeURIComponent(item.data.name));
                            }
                        },
                        {
                            css : 'p-l-xs',
                            sortInclude : false,
                            custom : FolderPicker.selectView
                        }
                    ];
                }
            });
        }
    };

    self.showFolders = ko.computed(function(){
        return self.nodeHasAuth() && self.userIsOwner();
    });

    self.cancelSelection = function() {
        self.selected(null);
    };

    self.selectedFolderName = ko.computed(function() {
        var userIsOwner = self.userIsOwner();
        var selected = self.selected();
        return (userIsOwner && selected) ? decodeURIComponent(selected.name) : '';
    });

    function onSubmitSuccess(response) {
        self.currentFolder(decodeURIComponent(self.selected().name));
        self.changeMessage(
            'Successfully linked "' +
           $osf.htmlEscape(decodeURIComponent(self.selected().name)) +
            '". Go to the Files page to view your files.',
        'text-success', 5000);
        // Update folder in ViewModel
        self.urls(response.result.urls);
        self.cancelSelection();
    }

    function onSubmitError() {
        self.changeMessage('Could not change settings. Please try again later.', 'text-danger');
    }

    /**
     * Send a PUT request to change the linked Google Drive folder.
     */
    self.submitSettings = function() {
        $osf.putJSON(self.urls().config, ko.toJS(self))
            .done(onSubmitSuccess)
            .fail(onSubmitError);
    };
};

function GoogleDriveNodeConfig(selector, url, folderPicker) {
    // Initialization code
    var self = this;
    self.viewModel = new ViewModel(url, selector, folderPicker);
    $.osf.applyBindings(self.viewModel, selector);
}

module.exports = GoogleDriveNodeConfig;
<|endoftext|>


<commit_before>import Twit from 'twit';
import winston from 'winston';

const nbCachePosts = 46;

// Array of hashtags to exclude (noise)
const exclude = [
  'cat',
  'calumhood',
  'cloudporn',
  'dog',
  'followalways',
  'followback',
  'food',
  'like4like',
  'likeforlike',
  'lovethis',
  'natureporn',
  'porn',
  'selfie',
  'sex',
  'skyporn',
  'sneakerhead',
  'tagsforlikes',
  'xoxo',
];

function isPostExcluded(post) {
  const hashtags = post.entities.hashtags.map(val => val.text);
  for (let i = 0; i < hashtags.length; i++) {
    if (exclude.indexOf(hashtags[i]) !== -1) {
      return true;
    }
  }
  return false;
}

export default function (io) {
  // Init twitter lib
  const twitter = new Twit({
    consumer_key: process.env.TWITTER_CONSUMER_KEY,
    consumer_secret: process.env.TWITTER_CONSUMER_SECRET,
    access_token: process.env.TWITTER_ACCESS_TOKEN,
    access_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET,
  });

  // Init stream
  const stream = twitter.stream('statuses/filter', { track: '#sunset' });
  const posts = [];

  // Listen stream
  stream.on('tweet', (tweet) => {
    if (tweet && tweet.retweeted_status == null && tweet.entities != null &&
      tweet.entities.media != null && tweet.entities.media[0].media_url_https != null) {
      if (!isPostExcluded(tweet)) {
        // Add post in cache
        posts.push({
          image_url: tweet.entities.media[0].media_url_https,
        });
        io.sockets.emit('new:tweet', {
          image_url: tweet.entities.media[0].media_url_https,
        });
        // Only keep number cache max
        if (posts.length > nbCachePosts) {
          posts.splice(0, posts.length - nbCachePosts);
        }
      }
    }
  });

  io.on('connection', (socket) => {
    // On user connection send him posts cache
    posts.forEach((post) => {
      socket.emit('new:tweet', post);
    });
  });

  winston.log('info', 'Twitter stream started');
}
<commit_msg>Add exclude hashtags<commit_after>import Twit from 'twit';
import winston from 'winston';

const nbCachePosts = 46;

// Array of hashtags to exclude (noise)
const exclude = [
  'adultwork',
  'cat',
  'calumhood',
  'camgirls',
  'cammodels',
  'casualdates',
  'cloudporn',
  'dates',
  'dating',
  'dog',
  'flirt',
  'followalways',
  'followback',
  'food',
  'gay',
  'kiksex',
  'like4like',
  'likeforlike',
  'lovethis',
  'masturbation',
  'natureporn',
  'nudemodel',
  'onlinecammodel',
  'onlinedating',
  'phonesex',
  'porn',
  'pussy',
  'selfie',
  'sex',
  'skyporn',
  'snapchatme',
  'sneakerhead',
  'tagsforlikes',
  'xoxo',
];

function isPostExcluded(post) {
  const hashtags = post.entities.hashtags.map(val => val.text);
  for (let i = 0; i < hashtags.length; i++) {
    if (exclude.indexOf(hashtags[i]) !== -1) {
      return true;
    }
  }
  return false;
}

export default function (io) {
  // Init twitter lib
  const twitter = new Twit({
    consumer_key: process.env.TWITTER_CONSUMER_KEY,
    consumer_secret: process.env.TWITTER_CONSUMER_SECRET,
    access_token: process.env.TWITTER_ACCESS_TOKEN,
    access_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET,
  });

  // Init stream
  const stream = twitter.stream('statuses/filter', { track: '#sunset' });
  const posts = [];

  // Listen stream
  stream.on('tweet', (tweet) => {
    if (tweet && tweet.retweeted_status == null && tweet.entities != null &&
      tweet.entities.media != null && tweet.entities.media[0].media_url_https != null) {
      if (!isPostExcluded(tweet)) {
        // Add post in cache
        posts.push({
          image_url: tweet.entities.media[0].media_url_https,
        });
        io.sockets.emit('new:tweet', {
          image_url: tweet.entities.media[0].media_url_https,
        });
        // Only keep number cache max
        if (posts.length > nbCachePosts) {
          posts.splice(0, posts.length - nbCachePosts);
        }
      }
    }
  });

  io.on('connection', (socket) => {
    // On user connection send him posts cache
    posts.forEach((post) => {
      socket.emit('new:tweet', post);
    });
  });

  winston.log('info', 'Twitter stream started');
}
<|endoftext|>


<commit_before>jQuery(function ($, undefined) {
	$.fn.extend({
		autocompleteSelMul: function () {
			return this.not('.autocompleteSelMulInited').each(function () {
				var me = $(this).hide().addClass('autocompleteSelMulInited'),
					meCont = me.parent().is('.selectCont') ? me.parent().hide() : me,
					val,
					options,
					keepValue = me.data('keepvalue'),
					input = $('<input type="text" name="' + (keepValue && keepValue.length > 2 ? keepValue : me.attr('id') + '_new') + '" />').insertBefore(meCont),
					list = [],
					cur = [],
					sep = me.data('sep') || ',',
					sepJoin = me.data('sepjoin') || sep + ' ',
					split = function (val) {
						return val.split(new RegExp(sep + '\s*'));
					},
					extractLast = function (term) {
						return split(term).pop();
					},
					writeForm = function () {
						var tmp = split(input.val()),
							val = [],
							others = [];
						$.each(tmp, function () {
							var cur = $.trim(this),
								curOpt = options.filter('[rel="' + cur + '"]');
							if (curOpt.length) {
								val.push(curOpt.attr('value'));
							} else {
								others.push(cur);
							}
						});
						me.val(val);
						if (keepValue) {
							input.val(others.join(sepJoin));
						} else {
							input.attr('disabled', 'disabled');
						}
					},
					reenable = function () {
						input.removeAttr('disabled');
					},
					init = function () {
						options = me.children('option:not([disabled])');
						list = [];
						options.each(function () {
							var opt = $(this),
								txt = $.trim(opt.text());
							opt.attr('rel', txt);
							list.push({
								value: opt.attr('value'),
								label: txt
							});
						});
						val = me.val();
						cur = [];
						if (val && val.length) {
							$.each(val, function (k, v) {
								cur.push(options.filter('[value="' + v + '"]').text());
							});
							cur.push('');
						}
						input.val(cur.join(sepJoin));
					};

				me.removeProp('required').removeAttr('required');

				if (me.attr('placeholder')) {
					input.attr('placeholder', me.attr('placeholder'));
				}

				init();

				input.autocomplete({
					minLength: 1,
					source: function (request, response) {
						// delegate back to autocomplete, but extract the last term
						response($.ui.autocomplete.filter(list, $.trim(extractLast(request.term))));
					},
					focus: function () {
						return false;
					},
					search: function () {
						if ($.ui.version.indexOf('1.12') === 0) {
							// Fix bug in jQuery.ui somewhere where menu.bindings just grows and grows
							// See https://bugs.jqueryui.com/ticket/10050
							$(this).data('ui-autocomplete').menu.bindings = $();
						}
					},
					select: function (event, ui) {
						var terms = split(this.value);
						// remove the current input
						terms.pop();
						// add the selected item
						terms.push(ui.item.label);
						// add placeholder to get the comma-and-space at the end
						terms.push('');
						this.value = terms.join(sepJoin);
						$(this).trigger('change');
						return false;
					}
				});

				me
					.on('autocompleteWriteForm', writeForm)
					.on('autocompleteReenable', reenable)
					.on('autocompleteReinit', init)
					.closest('form').on('submit', function (e) {
						if (!e.isDefaultPrevented()) {
							writeForm();
						}
					});
			}).end();
		},
		autocompleteSel: function () {
			return this.not('.autocompleteSelInited').each(function () {
				var me = $(this).hide().addClass('autocompleteSelInited'),
					meCont = me.parent().is('.selectCont') ? me.parent().hide() : me,
					val,
					options,
					input = $('<input type="text" name="' + me.attr('id') + '_new" ' + (me.attr('required') ? 'required="required"' : '') + '/>').insertBefore(meCont),
					list,
					writeForm = function () {
						var val = input.val(),
							curOpt = options.filter('[rel="' + $.trim(val) + '"]');
						if (curOpt.length) {
							me.val(curOpt.attr('value'));
						}
						input.attr('disabled', 'disabled');
					},
					reenable = function () {
						input.removeAttr('disabled');
					},
					init = function () {
						options = me.children('option:not([disabled])');
						list = [];
						options.each(function () {
							var opt = $(this),
								txt = $.trim(opt.text());
							opt.attr('rel', txt);
							list.push({
								value: opt.attr('value'),
								label: txt
							});
						});
						val = me.val();
						if (val && val.length) {
							input.val(options.filter('[value="' + val + '"]').text());
						} else {
							input.val('');
						}
					};

				me.removeProp('required').removeAttr('required');
				if (me.attr('placeholder')) {
					input.attr('placeholder', me.attr('placeholder'));
				}

				init();

				input.autocomplete({
					minLength: 1,
					source: function (request, response) {
						// delegate back to autocomplete, but extract the last term
						response($.ui.autocomplete.filter(list, $.trim(request.term)));
					},
					focus: function () {
						return false;
					},
					search: function () {
						if ($.ui.version.indexOf('1.12') === 0) {
							// Fix bug in jQuery.ui somewhere where menu.bindings just grows and grows
							// See https://bugs.jqueryui.com/ticket/10050
							$(this).data('ui-autocomplete').menu.bindings = $();
						}
					},
					select: function (event, ui) {
						this.value = ui.item.label;
						$(this).trigger('change');
						me.val(ui.item.value).trigger('change');
						return false;
					}
				});

				me
					.on('autocompleteWriteForm', writeForm)
					.on('autocompleteReenable', reenable)
					.on('autocompleteReinit', init)
					.closest('form').on('submit', function (e) {
						if (!e.isDefaultPrevented()) {
							writeForm();
						}
					});
			}).end();
		}
	});

	$('select.autocompleteSelMul').autocompleteSelMul();
	$('select.autocompleteSel').autocompleteSel();
});
<commit_msg>Add append method for autocomplete<commit_after>jQuery(function ($, undefined) {
	$.fn.extend({
		autocompleteSelMul: function () {
			return this.not('.autocompleteSelMulInited').each(function () {
				var me = $(this).hide().addClass('autocompleteSelMulInited'),
					meCont = me.parent().is('.selectCont') ? me.parent().hide() : me,
					val,
					options,
					keepValue = me.data('keepvalue'),
					input = $('<input type="text" name="' + (keepValue && keepValue.length > 2 ? keepValue : me.attr('id') + '_new') + '" />').insertBefore(meCont),
					list = [],
					cur = [],
					sep = me.data('sep') || ',',
					sepJoin = me.data('sepjoin') || sep + ' ',
					split = function (val) {
						return val.split(new RegExp(sep + '\s*'));
					},
					extractLast = function (term) {
						return split(term).pop();
					},
					writeForm = function () {
						var tmp = split(input.val()),
							val = [],
							others = [];
						$.each(tmp, function () {
							var cur = $.trim(this),
								curOpt = options.filter('[rel="' + cur + '"]');
							if (curOpt.length) {
								val.push(curOpt.attr('value'));
							} else {
								others.push(cur);
							}
						});
						me.val(val);
						if (keepValue) {
							input.val(others.join(sepJoin));
						} else {
							input.attr('disabled', 'disabled');
						}
					},
					reenable = function () {
						input.removeAttr('disabled');
					},
					init = function () {
						options = me.children('option:not([disabled])');
						list = [];
						options.each(function () {
							var opt = $(this),
								txt = $.trim(opt.text());
							opt.attr('rel', txt);
							list.push({
								value: opt.attr('value'),
								label: txt
							});
						});
						val = me.val();
						cur = [];
						if (val && val.length) {
							$.each(val, function (k, v) {
								cur.push(options.filter('[value="' + v + '"]').text());
							});
							cur.push('');
						}
						input.val(cur.join(sepJoin));
						input.trigger('change');
					},
					append = function (text) {
						var terms = split(input.val());
						terms.pop();
						// remove the current input
						terms.push(text);
						// add the selected item
						terms.push('');
						// add placeholder to get the comma-and-space at the end
						var str = terms.join(sepJoin);
						str = str.replace(/ +(?= )/g, ''); //remove useless multispacing
						input.val(str);
						input.trigger('change');
					};

				me.removeProp('required').removeAttr('required');

				if (me.attr('placeholder')) {
					input.attr('placeholder', me.attr('placeholder'));
				}

				init();

				input.autocomplete({
					minLength: 1,
					source: function (request, response) {
						// delegate back to autocomplete, but extract the last term
						response($.ui.autocomplete.filter(list, $.trim(extractLast(request.term))));
					},
					focus: function () {
						return false;
					},
					search: function () {
						if ($.ui.version.indexOf('1.12') === 0) {
							// Fix bug in jQuery.ui somewhere where menu.bindings just grows and grows
							// See https://bugs.jqueryui.com/ticket/10050
							$(this).data('ui-autocomplete').menu.bindings = $();
						}
					},
					select: function (event, ui) {
						append(ui.item.label);
						return false;
					}
				});

				me
					.on('autocompleteAppend', function (e, params) {
						append(params.text);
					})
					.on('autocompleteWriteForm', writeForm)
					.on('autocompleteReenable', reenable)
					.on('autocompleteReinit', init)
					.closest('form').on('submit', function (e) {
						if (!e.isDefaultPrevented()) {
							writeForm();
						}
					});
			}).end();
		},
		autocompleteSel: function () {
			return this.not('.autocompleteSelInited').each(function () {
				var me = $(this).hide().addClass('autocompleteSelInited'),
					meCont = me.parent().is('.selectCont') ? me.parent().hide() : me,
					val,
					options,
					input = $('<input type="text" name="' + me.attr('id') + '_new" ' + (me.attr('required') ? 'required="required"' : '') + '/>').insertBefore(meCont),
					list,
					writeForm = function () {
						var val = input.val(),
							curOpt = options.filter('[rel="' + $.trim(val) + '"]');
						if (curOpt.length) {
							me.val(curOpt.attr('value'));
						}
						input.attr('disabled', 'disabled');
					},
					reenable = function () {
						input.removeAttr('disabled');
					},
					init = function () {
						options = me.children('option:not([disabled])');
						list = [];
						options.each(function () {
							var opt = $(this),
								txt = $.trim(opt.text());
							opt.attr('rel', txt);
							list.push({
								value: opt.attr('value'),
								label: txt
							});
						});
						val = me.val();
						if (val && val.length) {
							input.val(options.filter('[value="' + val + '"]').text());
						} else {
							input.val('');
						}
					};

				me.removeProp('required').removeAttr('required');
				if (me.attr('placeholder')) {
					input.attr('placeholder', me.attr('placeholder'));
				}

				init();

				input.autocomplete({
					minLength: 1,
					source: function (request, response) {
						// delegate back to autocomplete, but extract the last term
						response($.ui.autocomplete.filter(list, $.trim(request.term)));
					},
					focus: function () {
						return false;
					},
					search: function () {
						if ($.ui.version.indexOf('1.12') === 0) {
							// Fix bug in jQuery.ui somewhere where menu.bindings just grows and grows
							// See https://bugs.jqueryui.com/ticket/10050
							$(this).data('ui-autocomplete').menu.bindings = $();
						}
					},
					select: function (event, ui) {
						this.value = ui.item.label;
						$(this).trigger('change');
						me.val(ui.item.value).trigger('change');
						return false;
					}
				});

				me
					.on('autocompleteWriteForm', writeForm)
					.on('autocompleteReenable', reenable)
					.on('autocompleteReinit', init)
					.closest('form').on('submit', function (e) {
						if (!e.isDefaultPrevented()) {
							writeForm();
						}
					});
			}).end();
		}
	});

	$('select.autocompleteSelMul').autocompleteSelMul();
	$('select.autocompleteSel').autocompleteSel();
});<|endoftext|>


<commit_before>var env = process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var path = require( 'path' );
var fs = require( 'fs' );
var express = require( 'express' );
var logger = require( 'morgan' );
var app = express();

app.set( 'port', process.env.PORT || 3000 );
app.use( logger( 'dev' ));

if ( app.get( 'env' ) === 'development' ) {
  app.use( express.static( path.join( __dirname, '../front/main' ) ) );
  app.use( '/test-doubles', express.static( path.join( __dirname, '../front/test/unit/test-doubles' ) ) );
} else if ( app.get( 'env' ) === 'build-debug' ) {
  app.use( express.static( path.join( __dirname, '../public' ) ) );
  app.use( '/jspm_packages', express.static( path.join( __dirname, '../front/main/jspm_packages' ) ) );
} else {
  app.use( express.static( path.join( __dirname, '../public' ) ) );
  app.use( '/jspm_packages', express.static( path.join( __dirname, '../front/main/jspm_packages' ) ) );
}

///**
// * ERROR HANDLING
// */
///// catch 404 and forwarding to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

///// error handlers
//
//// development error handler
//// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    });
  });
}

//// production error handler
//// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});

app.listen( app.get( 'port' ), function () {
  console.log( 'Express server listening in env ' + env + ' on port ' + app.get( 'port' ));
});
<commit_msg>fix style issues<commit_after>var env = process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var path = require( 'path' );
var fs = require( 'fs' );
var express = require( 'express' );
var logger = require( 'morgan' );
var app = express();

app.set( 'port', process.env.PORT || 3000 );
app.use( logger( 'dev' ));

if ( app.get( 'env' ) === 'development' ) {
  app.use( express.static( path.join( __dirname, '../front/main' )));
  app.use( '/test-doubles', express.static(
    path.join( __dirname, '../front/test/unit/test-doubles' )));
} else if ( app.get( 'env' ) === 'build-debug' ) {
  app.use( express.static( path.join( __dirname, '../public' )));
  app.use( '/jspm_packages', express.static(
    path.join( __dirname, '../front/main/jspm_packages' )));
} else {
  app.use( express.static( path.join( __dirname, '../public' )));
  app.use( '/jspm_packages', express.static(
    path.join( __dirname, '../front/main/jspm_packages' )));
}

///**
// * ERROR HANDLING
// */
///// catch 404 and forwarding to error handler
app.use( function ( req, res, next ) {
  var err = new Error( 'Not Found' );
  err.status = 404;
  next( err );
});

///// error handlers
//
//// development error handler
//// will print stacktrace
if ( app.get( 'env' ) === 'development' ) {
  app.use( function ( err, req, res, next ) {
    res.status( err.status || 500 );
    res.render( 'error', {
      message : err.message,
      error   : err
    });
  });
}

//// production error handler
//// no stacktraces leaked to user
app.use( function ( err, req, res, next ) {
  res.status( err.status || 500 );
  res.render( 'error', {
    message : err.message,
    error   : {}
  });
});

app.listen( app.get( 'port' ), function () {
  console.log( 'Express server listening in env ' + env +
    ' on port ' + app.get( 'port' ));
});
<|endoftext|>


<commit_before>(function (module) {
    module.controller('projectHome', projectHome);
    projectHome.$inject = ["project"];

    function projectHome(project, ui) {
        var ctrl = this;
        ctrl.project = project;
    }

}(angular.module('materialscommons')));
<commit_msg>Remove reference to unused ui service.<commit_after>(function (module) {
    module.controller('projectHome', projectHome);
    projectHome.$inject = ["project"];

    function projectHome(project) {
        var ctrl = this;
        ctrl.project = project;
    }

}(angular.module('materialscommons')));
<|endoftext|>


<commit_before>// utility functions

export function getUserId () {
  return getUserProfile().user_id || null
}

export function getUserEmail () {
  return getUserProfile().email || null

}

export function getUserProfile () {
  return JSON.parse(localStorage.getItem('profile') || '{}')
}

export function isAuthenticated () {
  const profile = localStorage.getItem('profile')
  const idToken = localStorage.getItem('id_token')
  if (profile == null) return false
  if (idToken == null) return false
  return true
}

export function makeActionName (moduleName, actionName) {
  return `${moduleName}:${actionName}`
}
<commit_msg>Feature 581 bring back fake user (#259)<commit_after>// utility functions

export function getUserId () {
  return getUserProfile().user_id || null
}

export function getUserEmail () {
  return getUserProfile().email || null
}

export function getUserProfile () {
  return JSON.parse(localStorage.getItem('profile') || '{}')
}

export function isAuthenticated () {
  const profile = localStorage.getItem('profile')
  const idToken = localStorage.getItem('id_token')
  if (profile == null) return false
  if (idToken == null) return false
  return true
}

export function makeActionName (moduleName, actionName) {
  return `${moduleName}:${actionName}`
}
<|endoftext|>


<commit_before>/**
 * Based on aframe/examples/showcase/tracked-controls.
 *
 * Implement bounding sphere collision detection for entities with a mesh.
 * Sets the specified state on the intersected entities.
 *
 * @property {string} objects - Selector of the entities to test for collision.
 * @property {string} state - State to set on collided entities.
 *
 */
module.exports = {
  schema: {
    objects: {default: ''},
    state: {default: 'collided'},
    radius: {default: 0.05}
  },

  init: function () {
    this.els = [];
    this.collisions = [];
  },

  /**
   * Update list of entities to test for collision.
   */
  update: function () {
    var data = this.data;
    var objectEls;

    // Push entities into list of els to intersect.
    if (data.objects) {
      objectEls = this.el.sceneEl.querySelectorAll(data.objects);
    } else {
      // If objects not defined, intersect with everything.
      objectEls = this.el.sceneEl.children;
    }
    // Convert from NodeList to Array
    this.els = Array.prototype.slice.call(objectEls);
  },

  tick: (function () {
    var position = new THREE.Vector3(),
        meshPosition = new THREE.Vector3();
    return function () {
      var el = this.el,
          data = this.data,
          mesh = el.getObject3D('mesh'),
          collisions = [];

      if (!mesh) { return; }

      position.copy(el.object3D.getWorldPosition());

      // Update collisions.
      this.els.forEach(intersect);
      // Emit events.
      collisions.forEach(handleHit);
      // No collisions.
      if (collisions.length === 0) { el.emit('hit', {el: null}); }
      // Updated the state of the elements that are not intersected anymore.
      this.collisions.filter(function (el) {
        return collisions.indexOf(el) === -1;
      }).forEach(function removeState (el) {
        el.removeState(data.state);
      });
      // Store new collisions
      this.collisions = collisions;

      // AABB collision detection
      function intersect (el) {
        var radius, mesh;
        if ( !el.isEntity ) { return; }
        mesh = el.getObject3D('mesh');

        if (!mesh || !mesh.geometry) return;

        mesh.getWorldPosition(meshPosition);
        mesh.geometry.computeBoundingSphere();
        radius = mesh.geometry.boundingSphere.radius;
        if (position.distanceTo(meshPosition) < radius + data.radius) {
          collisions.push(el);
        }
      }

      function handleHit (hitEl) {
        hitEl.emit('hit');
        hitEl.addState(data.state);
        el.emit('hit', {el: hitEl});
      }
    };
  })()
};
<commit_msg>Clean up.<commit_after>/**
 * Based on aframe/examples/showcase/tracked-controls.
 *
 * Implement bounding sphere collision detection for entities with a mesh.
 * Sets the specified state on the intersected entities.
 *
 * @property {string} objects - Selector of the entities to test for collision.
 * @property {string} state - State to set on collided entities.
 *
 */
module.exports = {
  schema: {
    objects: {default: ''},
    state: {default: 'collided'},
    radius: {default: 0.05}
  },

  init: function () {
    this.els = [];
    this.collisions = [];
  },

  /**
   * Update list of entities to test for collision.
   */
  update: function () {
    var data = this.data;
    var objectEls;

    // Push entities into list of els to intersect.
    if (data.objects) {
      objectEls = this.el.sceneEl.querySelectorAll(data.objects);
    } else {
      // If objects not defined, intersect with everything.
      objectEls = this.el.sceneEl.children;
    }
    // Convert from NodeList to Array
    this.els = Array.prototype.slice.call(objectEls);
  },

  tick: (function () {
    var position = new THREE.Vector3(),
        meshPosition = new THREE.Vector3();
    return function () {
      var el = this.el,
          data = this.data,
          mesh = el.getObject3D('mesh'),
          collisions = [];

      if (!mesh) { return; }

      position.copy(el.object3D.getWorldPosition());

      // Update collisions.
      this.els.forEach(intersect);
      // Emit events.
      collisions.forEach(handleHit);
      // No collisions.
      if (collisions.length === 0) { el.emit('hit', {el: null}); }
      // Updated the state of the elements that are not intersected anymore.
      this.collisions.filter(function (el) {
        return collisions.indexOf(el) === -1;
      }).forEach(function removeState (el) {
        el.removeState(data.state);
      });
      // Store new collisions
      this.collisions = collisions;

      // AABB collision detection
      function intersect (el) {
        var radius, mesh;

        if (!el.isEntity) { return; }

        mesh = el.getObject3D('mesh');

        if (!mesh || !mesh.geometry) { return; }

        mesh.getWorldPosition(meshPosition);
        mesh.geometry.computeBoundingSphere();
        radius = mesh.geometry.boundingSphere.radius;
        if (position.distanceTo(meshPosition) < radius + data.radius) {
          collisions.push(el);
        }
      }

      function handleHit (hitEl) {
        hitEl.emit('hit');
        hitEl.addState(data.state);
        el.emit('hit', {el: hitEl});
      }
    };
  })()
};
<|endoftext|>


<commit_before>import React from 'react';

import CodeMirror from 'react-codemirror';

import { updateCellSource } from '../../actions';

import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/hint/anyword-hint';

export default class Editor extends React.Component {
  static propTypes = {
    id: React.PropTypes.string,
    input: React.PropTypes.any,
    getCompletions: React.PropTypes.any,
    language: React.PropTypes.string,
    lineNumbers: React.PropTypes.bool,
    onChange: React.PropTypes.func,
    theme: React.PropTypes.string,
    focused: React.PropTypes.bool,
  };

  static contextTypes = {
    dispatch: React.PropTypes.func,
  };

  static defaultProps = {
    language: 'python',
    lineNumbers: false,
    text: '',
    theme: 'composition',
    focused: false,
  };

  constructor(props) {
    super(props);
    this.state = {
      source: this.props.input,
    };
    this.onChange = this.onChange.bind(this);

    this.hint = this.completions.bind(this);
    this.hint.async = true;
  }

  componentDidMount() {
    // On first load, if focused, set codemirror to focus
    if (this.props.focused) {
      this.refs.codemirror.focus();
    }
    this.refs.codemirror.getCodeMirror().on('change', (cm, change) => {
      if (change.origin === '+input') {
        cm.execCommand('autocomplete');
      }
    });
  }

  componentWillReceiveProps(nextProps) {
    this.setState({
      source: nextProps.input,
    });
  }

  componentDidUpdate(prevProps) {
    if (this.props.focused && prevProps.focused !== this.props.focused) {
      this.refs.codemirror.focus();
    } else if (!this.props.focused && prevProps.focused !== this.props.focused) {
      const cm = this.refs.codemirror.getCodeMirror();
      cm.getInputField().blur();
    }
  }

  onChange(text) {
    if (this.props.onChange) {
      this.props.onChange(text);
    } else {
      this.setState({
        source: text,
      });
      this.context.dispatch(updateCellSource(this.props.id, text));
    }
  }

  completions(editor, callback) {
    const cursor = editor.getCursor();
    this.props.getCompletions(editor.getValue(), cursor.ch).then(results => callback({
      list: results.matches,
      from: {
        line: cursor.line,
        ch: results.cursor_start,
      },
      to: {
        line: cursor.line,
        ch: results.cursor_end,
      },
    }));
  }

  render() {
    const options = {
      mode: this.props.language,
      lineNumbers: this.props.lineNumbers,
      theme: this.props.theme,
      autofocus: false,
      hintOptions: {
        hint: this.hint,
      },
      extraKeys: {
      },
    };
    return (

        <CodeMirror
          value={this.state.source}
          ref="codemirror"
          className="cell_cm"
          options={options}
          onChange={this.onChange}
        />

    );
  }
}
<commit_msg>Always on autocomplete<commit_after>import React from 'react';

import CodeMirror from 'react-codemirror';

const Rx = require('@reactivex/rxjs');

import { updateCellSource } from '../../actions';

import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/hint/anyword-hint';

export default class Editor extends React.Component {
  static propTypes = {
    id: React.PropTypes.string,
    input: React.PropTypes.any,
    getCompletions: React.PropTypes.any,
    language: React.PropTypes.string,
    lineNumbers: React.PropTypes.bool,
    onChange: React.PropTypes.func,
    theme: React.PropTypes.string,
    focused: React.PropTypes.bool,
  };

  static contextTypes = {
    dispatch: React.PropTypes.func,
  };

  static defaultProps = {
    language: 'python',
    lineNumbers: false,
    text: '',
    theme: 'composition',
    focused: false,
  };

  constructor(props) {
    super(props);
    this.state = {
      source: this.props.input,
    };
    this.onChange = this.onChange.bind(this);

    this.hint = this.completions.bind(this);
    this.hint.async = true;
  }

  componentDidMount() {
    // On first load, if focused, set codemirror to focus
    if (this.props.focused) {
      this.refs.codemirror.focus();
    }

    const inputEvents = Rx.Observable.fromEvent(this.refs.codemirror.getCodeMirror(),
      'change', (cm, change) => {
        return {
          cm,
          change,
        };
      })
      .filter(x => x.change.origin === '+input');

    // TODO: The subscription created here needs to be cleaned up when the cell
    //       is deleted
    inputEvents
      .debounceTime(20)
      .subscribe(event => event.cm.execCommand('autocomplete'));
  }

  componentWillReceiveProps(nextProps) {
    this.setState({
      source: nextProps.input,
    });
  }

  componentDidUpdate(prevProps) {
    if (this.props.focused && prevProps.focused !== this.props.focused) {
      this.refs.codemirror.focus();
    } else if (!this.props.focused && prevProps.focused !== this.props.focused) {
      const cm = this.refs.codemirror.getCodeMirror();
      cm.getInputField().blur();
    }
  }

  onChange(text) {
    if (this.props.onChange) {
      this.props.onChange(text);
    } else {
      this.setState({
        source: text,
      });
      this.context.dispatch(updateCellSource(this.props.id, text));
    }
  }

  completions(editor, callback) {
    const cursor = editor.getCursor();
    this.props.getCompletions(editor.getValue(), cursor.ch).then(results => callback({
      list: results.matches,
      from: {
        line: cursor.line,
        ch: results.cursor_start,
      },
      to: {
        line: cursor.line,
        ch: results.cursor_end,
      },
    }));
  }

  render() {
    const options = {
      mode: this.props.language,
      lineNumbers: this.props.lineNumbers,
      theme: this.props.theme,
      autofocus: false,
      hintOptions: {
        hint: this.hint,
        completeSingle: false, // In automatic autocomplete mode we don't want override
      },
      extraKeys: {
        'Ctrl-Space': 'autocomplete',
      },
    };
    return (

        <CodeMirror
          value={this.state.source}
          ref="codemirror"
          className="cell_cm"
          options={options}
          onChange={this.onChange}
        />

    );
  }
}
<|endoftext|>


<commit_before>/**
 * jQuery Steam - TF2Items plugin
 *
 * Licensed under MIT LICENSE:
 *  http://www.opensource.org/licenses/mit-license.php
 *
 * Depencies:
 *  jQuery [of course]
 *  WebAPI plugin
 *
 * With the methods provided by this plugin, you will be
 * able to have an always-updated player backpack. Since
 * the data is REALLY big, it will take some time to load
 * the data [about 5 seconds on a good connection].
 *
 * Some stuff on this plugin are really ugly, I'll try to
 * update it ASAP.
 */

(function($){
  var tf2items = (function(){
    // Game defines
    var classes = ['scout', 'sniper', 'soldier', 'demoman', 'medic', 'heavy', 'pyro', 'spy', 'engineer'];
    var particles = ['particle0', 'particle1', 'particle2', 'particle3', 'particle4', 'particle5',
                     'Green Confetti',  'Purple Confetti', 'Haunted Ghosts', 'Green Energy', 'Purple Energy',
                     'Circling TF Logo', 'Massed Flies', 'Burning Flames', 'Scorching Flames', 'Searing Plasma',
                     'Vivid Plasma', 'Sunbeams', 'Circling Peace Sign', 'Circling Heart'];

    // Attributes that shouldn't be added to items
    var attBlackList = [25, 37, 116, 142, 189, 195, 196];
    // Items that have limited uses number
    var limitedItems = [241, 280, 281, 282, 283, 284, 286, 287, 288, 289, 290, 291,
                        5020, 5021, 5023, 5027, 5028, 5029, 5030, 5031, 5032, 5033,
                        5034, 5035, 5036, 5037, 5038, 5039, 5040, 5042, 5043, 5044];
    // Strings that come unstralated from the server
    var unstraslated = {
      TF_Wearable_Hat: 'Hat',
      TF_LockedCrate: 'Crate'
    };

    var schema = null;
    var language = null;

    // Parse the inventory token to its real values
    // http://wiki.teamfortress.com/wiki/WebAPI#Inventory_Token
    var parseInventory = function(inv) {
      var val = Number(inv);
      var equipped = [];
      for(var n = 0; n < 9; n++) {
        if((val & 1 << (n + 16))) {
          equipped.push(classes[n]);
        }
      }

      return {
          position: val & 0xFFFF,
          equipped: equipped
      };
    };

    // Load the item data, parsing the Schema value
    var loadSchema = function(callback) {
      if(schema) {
        callback();
      }
      else {
        $.steam.webapi.tf2.schema((language || 'en'), function(data) {
          var items = data.result.items.item;
          var qualities = data.result.qualities;
          var qualityNames = data.result.qualityNames;
          var attributes = data.result.attributes.attribute;

          schema = {
            qualities: [],
            items: [],
            attributes: {}
          };

          for(var q in qualities) {
            schema.qualities[qualities[q]] = {
              code: q,
              name: qualityNames[q]
            };
          }

          for(var i in items) {
            var itm = items[i];
            schema.items[itm.defindex] = itm;
          }

          // Store the attributes indexed by their names. This optimizes the search
          for(var a in attributes) {
            var att = attributes[a];
            schema.attributes[att.name] = att;
          }

          callback();
        });
      }
    };

    // Build the item name
    var buildName = function(defindex, quality) {
      var ret = '';

      if(schema) {
        var name = schema.items[defindex].item_name;

        // This will allow us to get default item data
        if(quality) {
          var title = schema.qualities[quality].name;
          // If we the quality has a title [not unique], use it
          if(title !== 'Unique') {
            // If the name is like "The Cloack and Dagger" we insert the title after "The "
            // We could use schema.items[itm.defindex].proper_name to check this, but it's buggy
            if(name.indexOf("The ") === 0) {
              ret += "The ";
              name = name.substr(4);
            }

            ret += title + ' ';
          }
        }

        ret +=  name;
      }

      return ret;
    };

    // Get the list of item attributes
    var parseAttributes = function(defindex, extra) {
      var ret = [];

      if(schema) {
        // If the item has default attributes, parse them
        if(schema.items[defindex].attributes) {
          var attributes = schema.items[defindex].attributes.attribute;
          for(var a in attributes) {
            var att = attributes[a];
            var data = schema.attributes[att.name];
            if(validAttribute(data.defindex, att)) {
              ret.push({
                name: buildAttribute({
                  string: data.description_string || null,
                  format: data.description_format || null,
                  value: att.value,
                  float_value: att.float_value
                }),
                type: data.effect_type
              });
            }
          }
        }
        // Read extras attributes, if any
        if(extra) {
          var n = 0;
          for(var e in extra) {
            var att = extra[e];
            // Bypass the crate series from the item
            if(validAttribute(att.defindex, att) && att.defindex != 187) {
              var data = searchAttribute(att.defindex);
              ret.push({
                name: buildAttribute({
                  string: data.description_string || null,
                  format: data.description_format || null,
                  value: att.value,
                  float_value: att.float_value
                }),
                type: data.effect_type
              });
            }
          }
        }
      }
      return ret;
    };

    // Check if the attribute info matches with any bad one
    var validAttribute = function(defindex, data) {
      if(!(data.value || data.float_value)) {
        return false;
      }
      for(var i in attBlackList) {
        if(attBlackList[i] == defindex) {
          return false;
        }
      }
      return true;
    }

    // Get the attribute by its defindex
    var searchAttribute = function(defindex) {
      for(var a in schema.attributes) {
        if(schema.attributes[a].defindex == defindex){
          return schema.attributes[a];
        }
      }
    };

    // Build the attribute value string
    var buildAttribute = function(data) {
      var ret = '';

      if(data.format) {
        var value;

        switch(data.format) {
          case 'value_is_percentage':
            value = Math.round((data.value*100) - 100);
          break;
          case 'value_is_inverted_percentage':
            value = Math.round((100 - (data.value*100)));
          break;
          case 'value_is_additive':
            value = data.value;
          break;
          case 'value_is_additive_percentage':
            value = Math.round(data.value*100);
          break;
          case 'value_is_date':
            var date = new Date(data.value*1000);
            value = date.toLocaleDateString();
          break;
          case 'value_is_account_id':
            value = 'STEAM_0:'+(data.value%2)+':'+(data.value/2);
          break;
          case 'value_is_particle_index':
            value = particles[(data.value || data.float_value)];
          break;
          default:
            value = "V:"+data.value+"F:"+data.format;
        }

        ret = data.string.replace("%s1", value);
      }
      else {
        ret = data.string;
      }

      return ret;
    };

    // Check and get the color of a painted item
    var itemPainted = function(itm) {
      var data = schema.items[itm.defindex];
      var attributes = data.attributes ? data.attributes.attribute : null;
      for(var a in attributes) {
        var att = attributes[a];
        if(att.name == 'set item tint RGB') {
          return parseInt(att.value).toString(16);
        }
      }
      var extra = itm.attributes ? itm.attributes.attribute : null;
      for(var e in extra) {
        var att = extra[e];
        if(att.defindex == 142) {
          return parseInt(att.float_value).toString(16);
        }
      }

      return false;
    };

    // Check and get who gifted the item
    var itemGifted = function(itm) {
      var attributes = itm.attributes ? itm.attributes.attribute : null;
      for(var a in attributes) {
        var att = attributes[a];
        if(att.defindex == 186) {
          return att.value + 76561197960265728;
        }
      }

      return false;
    };

    var itemLimited = function(defindex) {
      for(var i in limitedItems) {
        if(limitedItems[i] == defindex) {
          return true;
        }
      }
      return false;
    };

    var fixString = function(str) {
      if(unstraslated[str]) {
        return unstraslated[str];
      }

      return str;
    }

    return {
      // Get and parse the items for a specific backpack
      getItems: function(id, callback) {
        $.steam.webapi.tf2.playerItems(id, function(data){
          var items = data.result.items.item;
          // Ensure that our schema is loaded
          loadSchema(function() {
            var ret = [];
            // Parse the item data, to be readable
            var a = 0;
            for(var i in items) {
              var itm = items[i];
              var inv = parseInventory(itm.inventory);
              var data = schema.items[itm.defindex];
              var att = itm.attributes ? itm.attributes.attribute : null;
              var cur = {
                position: inv.position,
                name: buildName(itm.defindex, itm.quality),
                custom_name: itm.custom_name,
                type: fixString(data.item_type_name),
                level: itm.level,
                quality: schema.qualities[itm.quality].code,
                quantity: itemLimited(itm.defindex) ? itm.quantity : null,
                equipped: inv.equipped,
                // Itens for all classes dont have 'used_by_classes', so use the classes array
                used_by: data.used_by_classes || classes,
                // Check and negate if something block the item from be traded
                tradable: !(itm.flag_cannot_trade || (typeof data.tradable === 'undefined' ? false : !data.tradable)),
                // Hardcoded: get the name of the image
                image: data.image_url.substr(46),
                // Parse some usefull data from the attributes
                color: itemPainted(itm),
                gifted: itemGifted(itm),
                attributes: parseAttributes(itm.defindex, att)
              };

              ret.push(cur);
            }

            callback(ret);
          });
        });
      },
      getItemList: function(callback) {
        // Get the schema
        loadSchema(function() {
          var items = schema.items;
          var ret = [];
          // Parse the item data, to be readable
          var a = 0;
          for(var i in items) {
            var itm = items[i];
            var att = itm.attributes;
            var cur = {
              name: buildName(itm.defindex),
              type: fixString(itm.item_type_name),
              // Itens for all classes dont have 'used_by_classes', so use the classes array
              used_by: itm.used_by_classes || classes,
              // Check and negate if something block the item from be traded
              tradable: !(itm.flag_cannot_trade),
              // Hardcoded: get the name of the image
              image: itm.image_url.substr(46),
              // Parse some usefull data from the attributes
              attributes: parseAttributes(itm.defindex)
            };

            ret.push(cur);
          }

          callback(ret);
        });
      },
      // Allow us to use multiple languages
      setLanguage: function(_language){
        if(_language === null || (_language && typeof _language === 'string')) {
          language = _language;
          // Clear the itemData cache, since it has language specific stuff
          if(schema !== null) {
            schema = null;
          }
        }
      }
    }
  })();

  // Create the steam object on jQuery and expose the tf2items
  if(!$.steam || typeof $.steam !== 'object') {
    $.steam = {};
  }
  $.steam.tf2items = tf2items;
})(jQuery);<commit_msg>added invalid items to getItems function return<commit_after>/**
 * jQuery Steam - TF2Items plugin
 *
 * Licensed under MIT LICENSE:
 *  http://www.opensource.org/licenses/mit-license.php
 *
 * Depencies:
 *  jQuery [of course]
 *  WebAPI plugin
 *
 * With the methods provided by this plugin, you will be
 * able to have an always-updated player backpack. Since
 * the data is REALLY big, it will take some time to load
 * the data [about 5 seconds on a good connection].
 *
 * Some stuff on this plugin are really ugly, I'll try to
 * update it ASAP.
 */

(function($){
  var tf2items = (function(){
    // Game defines
    var classes = ['scout', 'sniper', 'soldier', 'demoman', 'medic', 'heavy', 'pyro', 'spy', 'engineer'];
    var particles = ['particle0', 'particle1', 'particle2', 'particle3', 'particle4', 'particle5',
                     'Green Confetti',  'Purple Confetti', 'Haunted Ghosts', 'Green Energy', 'Purple Energy',
                     'Circling TF Logo', 'Massed Flies', 'Burning Flames', 'Scorching Flames', 'Searing Plasma',
                     'Vivid Plasma', 'Sunbeams', 'Circling Peace Sign', 'Circling Heart'];

    // Attributes that shouldn't be added to items
    var attBlackList = [25, 37, 116, 142, 189, 195, 196];
    // Items that have limited uses number
    var limitedItems = [241, 280, 281, 282, 283, 284, 286, 287, 288, 289, 290, 291,
                        5020, 5021, 5023, 5027, 5028, 5029, 5030, 5031, 5032, 5033,
                        5034, 5035, 5036, 5037, 5038, 5039, 5040, 5042, 5043, 5044];
    // Strings that come unstralated from the server
    var unstraslated = {
      TF_Wearable_Hat: 'Hat',
      TF_LockedCrate: 'Crate'
    };

    var schema = null;
    var language = null;

    // Parse the inventory token to its real values
    // http://wiki.teamfortress.com/wiki/WebAPI#Inventory_Token
    var parseInventory = function(inv) {
      var val = Number(inv);
      var equipped = [];
      for(var n = 0; n < 9; n++) {
        if((val & 1 << (n + 16))) {
          equipped.push(classes[n]);
        }
      }

      return {
          position: val & 0xFFFF,
          equipped: equipped
      };
    };

    // Load the item data, parsing the Schema value
    var loadSchema = function(callback) {
      if(schema) {
        callback();
      }
      else {
        $.steam.webapi.tf2.schema((language || 'en'), function(data) {
          var items = data.result.items.item;
          var qualities = data.result.qualities;
          var qualityNames = data.result.qualityNames;
          var attributes = data.result.attributes.attribute;

          schema = {
            qualities: [],
            items: [],
            attributes: {}
          };

          for(var q in qualities) {
            schema.qualities[qualities[q]] = {
              code: q,
              name: qualityNames[q]
            };
          }

          for(var i in items) {
            var itm = items[i];
            schema.items[itm.defindex] = itm;
          }

          // Store the attributes indexed by their names. This optimizes the search
          for(var a in attributes) {
            var att = attributes[a];
            schema.attributes[att.name] = att;
          }

          callback();
        });
      }
    };

    // Build the item name
    var buildName = function(defindex, quality) {
      var ret = '';

      if(schema) {
        var name = schema.items[defindex].item_name;

        // This will allow us to get default item data
        if(quality) {
          var title = schema.qualities[quality].name;
          // If we the quality has a title [not unique], use it
          if(title !== 'Unique') {
            // If the name is like "The Cloack and Dagger" we insert the title after "The "
            // We could use schema.items[itm.defindex].proper_name to check this, but it's buggy
            if(name.indexOf("The ") === 0) {
              ret += "The ";
              name = name.substr(4);
            }

            ret += title + ' ';
          }
        }

        ret +=  name;
      }

      return ret;
    };

    // Get the list of item attributes
    var parseAttributes = function(defindex, extra) {
      var ret = [];

      if(schema) {
        // If the item has default attributes, parse them
        if(schema.items[defindex].attributes) {
          var attributes = schema.items[defindex].attributes.attribute;
          for(var a in attributes) {
            var att = attributes[a];
            var data = schema.attributes[att.name];
            if(validAttribute(data.defindex, att)) {
              ret.push({
                name: buildAttribute({
                  string: data.description_string || null,
                  format: data.description_format || null,
                  value: att.value,
                  float_value: att.float_value
                }),
                type: data.effect_type
              });
            }
          }
        }
        // Read extras attributes, if any
        if(extra) {
          var n = 0;
          for(var e in extra) {
            var att = extra[e];
            // Bypass the crate series from the item
            if(validAttribute(att.defindex, att) && att.defindex != 187) {
              var data = searchAttribute(att.defindex);
              ret.push({
                name: buildAttribute({
                  string: data.description_string || null,
                  format: data.description_format || null,
                  value: att.value,
                  float_value: att.float_value
                }),
                type: data.effect_type
              });
            }
          }
        }
      }
      return ret;
    };

    // Check if the attribute info matches with any bad one
    var validAttribute = function(defindex, data) {
      if(!(data.value || data.float_value)) {
        return false;
      }
      for(var i in attBlackList) {
        if(attBlackList[i] == defindex) {
          return false;
        }
      }
      return true;
    }

    // Get the attribute by its defindex
    var searchAttribute = function(defindex) {
      for(var a in schema.attributes) {
        if(schema.attributes[a].defindex == defindex){
          return schema.attributes[a];
        }
      }
    };

    // Build the attribute value string
    var buildAttribute = function(data) {
      var ret = '';

      if(data.format) {
        var value;

        switch(data.format) {
          case 'value_is_percentage':
            value = Math.round((data.value*100) - 100);
          break;
          case 'value_is_inverted_percentage':
            value = Math.round((100 - (data.value*100)));
          break;
          case 'value_is_additive':
            value = data.value;
          break;
          case 'value_is_additive_percentage':
            value = Math.round(data.value*100);
          break;
          case 'value_is_date':
            var date = new Date(data.value*1000);
            value = date.toLocaleDateString();
          break;
          case 'value_is_account_id':
            value = 'STEAM_0:'+(data.value%2)+':'+(data.value/2);
          break;
          case 'value_is_particle_index':
            value = particles[(data.value || data.float_value)];
          break;
          default:
            value = "V:"+data.value+"F:"+data.format;
        }

        ret = data.string.replace("%s1", value);
      }
      else {
        ret = data.string;
      }

      return ret;
    };

    // Check and get the color of a painted item
    var itemPainted = function(itm) {
      var data = schema.items[itm.defindex];
      var attributes = data.attributes ? data.attributes.attribute : null;
      for(var a in attributes) {
        var att = attributes[a];
        if(att.name == 'set item tint RGB') {
          return parseInt(att.value).toString(16);
        }
      }
      var extra = itm.attributes ? itm.attributes.attribute : null;
      for(var e in extra) {
        var att = extra[e];
        if(att.defindex == 142) {
          return parseInt(att.float_value).toString(16);
        }
      }

      return false;
    };

    // Check and get who gifted the item
    var itemGifted = function(itm) {
      var attributes = itm.attributes ? itm.attributes.attribute : null;
      for(var a in attributes) {
        var att = attributes[a];
        if(att.defindex == 186) {
          return att.value + 76561197960265728;
        }
      }

      return false;
    };

    var itemLimited = function(defindex) {
      for(var i in limitedItems) {
        if(limitedItems[i] == defindex) {
          return true;
        }
      }
      return false;
    };

    var fixString = function(str) {
      if(unstraslated[str]) {
        return unstraslated[str];
      }

      return str;
    }

    return {
      // Get and parse the items for a specific backpack
      getItems: function(id, callback) {
        $.steam.webapi.tf2.playerItems(id, function(data){
          var items = data.result.items.item;
          // Ensure that our schema is loaded
          loadSchema(function() {
            var ret = {
              items: [],
              invalid: []
            };
            // Parse the item data, to be readable
            var a = 0;
            for(var i in items) {
              var itm = items[i];
              var inv = parseInventory(itm.inventory);
              var data = schema.items[itm.defindex];
              var att = itm.attributes ? itm.attributes.attribute : null;
              var cur = {
                position: inv.position,
                name: buildName(itm.defindex, itm.quality),
                custom_name: itm.custom_name,
                type: fixString(data.item_type_name),
                level: itm.level,
                quality: schema.qualities[itm.quality].code,
                quantity: itemLimited(itm.defindex) ? itm.quantity : null,
                equipped: inv.equipped,
                // Itens for all classes dont have 'used_by_classes', so use the classes array
                used_by: data.used_by_classes || classes,
                // Check and negate if something block the item from be traded
                tradable: !(itm.flag_cannot_trade || (typeof data.tradable === 'undefined' ? false : !data.tradable)),
                // Hardcoded: get the name of the image
                image: data.image_url.substr(46),
                // Parse some usefull data from the attributes
                color: itemPainted(itm),
                gifted: itemGifted(itm),
                attributes: parseAttributes(itm.defindex, att)
              };

              // Add the item to the invalid array if it's overlaping other items
              if(inv.position && typeof ret.items[inv.position] === 'undefined') {
                ret.items[inv.position] = cur;
              }
              else {
                ret.invalid.push(cur);
              }
            }

            callback(ret);
          });
        });
      },
      getItemList: function(callback) {
        // Get the schema
        loadSchema(function() {
          var items = schema.items;
          var ret = [];
          // Parse the item data, to be readable
          var a = 0;
          for(var i in items) {
            var itm = items[i];
            var att = itm.attributes;
            var cur = {
              name: buildName(itm.defindex),
              type: fixString(itm.item_type_name),
              // Itens for all classes dont have 'used_by_classes', so use the classes array
              used_by: itm.used_by_classes || classes,
              // Check and negate if something block the item from be traded
              tradable: !(itm.flag_cannot_trade),
              // Hardcoded: get the name of the image
              image: itm.image_url.substr(46),
              // Parse some usefull data from the attributes
              attributes: parseAttributes(itm.defindex)
            };

            ret.push(cur);
          }

          callback(ret);
        });
      },
      // Allow us to use multiple languages
      setLanguage: function(_language){
        if(_language === null || (_language && typeof _language === 'string')) {
          language = _language;
          // Clear the itemData cache, since it has language specific stuff
          if(schema !== null) {
            schema = null;
          }
        }
      }
    }
  })();

  // Create the steam object on jQuery and expose the tf2items
  if(!$.steam || typeof $.steam !== 'object') {
    $.steam = {};
  }
  $.steam.tf2items = tf2items;
})(jQuery);<|endoftext|>


<commit_before>(function($) {

  // Setup
  // ----------

  // The fix is for webkit browsers only
  // [https://bugs.webkit.org/show_bug.cgi?id=91790]()

  if(!(/WebKit/.test(navigator.userAgent))) {
    // return functions that do nothing but support chaining
    $.fn.fixSVGStack = function() { return this; };
    $.fn.fixSVGStackBackground = function() { return this; };
    return;
  }

  // Enabled / disable support for a dirty CSS-Hack
  // if `USE_DIRTY_CSS_CONTENT_HACK` is true the following CSS enables the fix
  // – otherwise only inline styles can be fixed
  //
  // ```
  // .classname {
  //   background: transparent url('stack.svg#SVG') no-repeat top left;
  //   content: 'stack.svg#SVG'; /* use content to pass url to webkit fixSVGStackBackground */
  // }
  // ```
  //
  var USE_DIRTY_CSS_CONTENT_HACK = true;

  // The Fix
  // ----------

  // Reads SVG Stack via Ajax and returns one element as base64 encoded data-uri.
  function getdataURIFromStack(url, cb) {

    // `url` must be in the form filename.svg#id
    var parts = url.split('#');
    if(parts.length !== 2) {
      cb(null);
    }
    // Ajax request should get data from browser cache
    // (needs to be verified)
    $.ajax({
        // `parts[0]` contains filename.svg
        url: parts[0],
        // Read SVG as 'text', jQuerys XML Parsing is broken with SVGs
        dataType: 'text'
      })
      .done(function(xmlText, status, res) {
        var xml = (new window.DOMParser()).parseFromString(xmlText, "text/xml")
        // `parts[1]` contains id
        var svg = xml.getElementById(parts[1]);
        var svgString = (new XMLSerializer()).serializeToString(svg);
        var dataURI = 'data:image/svg+xml;charset=utf-8;base64,' + Base64.encode(svgString);
        cb(dataURI);
      });
  }

  // Fix for SVG Stacks in background

  $.fn.fixSVGStackBackground = function() {

    this.each(function() {

      var $el = $(this);

      // At the heart of the bug:
      // Both jquery's `$el.css('background-image')` and `getComputedStyle($el[0], null).backgroundImage`
      // return and url without the #target part;

      var url = $el[0].style.backgroundImage.slice(4, (- 1)).replace(/["']/g, '');

      // Here is the quick and dirty hack, if enabled

      if(USE_DIRTY_CSS_CONTENT_HACK) {
        // Read url form `style.content`, the css content property is used to transport the information
        var style = getComputedStyle($el[0], null);
        if(style.backgroundImage.indexOf('.svg') !== -1 && style.content.indexOf('.svg#') !== -1) {
          url = style.content.replace(/["']/g, '');
        }
      }

      if(url.indexOf('.svg#') === -1) {
        return;
      }

      getdataURIFromStack(url, function(dataURI) {
        // Replace background-image url with dataURI
        $el.css('background-image', 'url(' + dataURI + ')');
      });

    });
    return this;
  };

  // Fix for SVG Stacks in img Tags

  $.fn.fixSVGStack = function() {
    this.each(function() {

      var $el = $(this);
      var url = $el.attr('src');

      if(url.indexOf('.svg#') === -1) {
        return;
      }
      getdataURIFromStack(url, function(dataURI) {
        // Replace src with dataURI
        $el.attr('src', dataURI);
      });
    });
    return this;
  };

  // Helpers
  // ----------

  // Base64 module is used to create data urls
  //
  // taken from
  // http://www.webtoolkit.info/javascript-base64.html
  var Base64 = {

    _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
    encode : function (input) {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;

        input = Base64._utf8_encode(input);

        while (i < input.length) {

            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);

            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;

            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }

            output = output +
            this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
            this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

        }

        return output;
    },

    _utf8_encode : function (string) {
        string = string.replace(/\r\n/g,"\n");
        var utftext = "";

        for (var n = 0; n < string.length; n++) {

            var c = string.charCodeAt(n);

            if (c < 128) {
                utftext += String.fromCharCode(c);
            }
            else if((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        }

        return utftext;
    }
  }

})(jQuery);<commit_msg>no need for base64 encoding<commit_after>(function($) {

  // Setup
  // ----------

  // The fix is for webkit browsers only
  // [https://bugs.webkit.org/show_bug.cgi?id=91790]()

  if(!(/WebKit/.test(navigator.userAgent))) {
    // return functions that do nothing but support chaining
    $.fn.fixSVGStack = function() { return this; };
    $.fn.fixSVGStackBackground = function() { return this; };
    return;
  }

  // Enabled / disable support for a dirty CSS-Hack
  // if `USE_DIRTY_CSS_CONTENT_HACK` is true the following CSS enables the fix
  // – otherwise only inline styles can be fixed
  //
  // ```
  // .classname {
  //   background: transparent url('stack.svg#SVG') no-repeat top left;
  //   content: 'stack.svg#SVG'; /* use content to pass url to webkit fixSVGStackBackground */
  // }
  // ```
  //
  var USE_DIRTY_CSS_CONTENT_HACK = true;

  // The Fix
  // ----------

  // Reads SVG Stack via Ajax and returns one element as base64 encoded data-uri.
  function getdataURIFromStack(url, cb) {

    // `url` must be in the form filename.svg#id
    var parts = url.split('#');
    if(parts.length !== 2) {
      cb(null);
    }
    // Ajax request should get data from browser cache
    // (needs to be verified)
    $.ajax({
        // `parts[0]` contains filename.svg
        url: parts[0],
        // Read SVG as 'text', jQuerys XML Parsing is broken with SVGs
        dataType: 'text'
      })
      .done(function(xmlText, status, res) {
        var xml = (new window.DOMParser()).parseFromString(xmlText, "text/xml")
        // `parts[1]` contains id
        var svg = xml.getElementById(parts[1]);
        var svgString = (new XMLSerializer()).serializeToString(svg);
        var dataURI = 'data:image/svg+xml;utf-8,' + escape(svgString);
        cb(dataURI);
      });
  }

  // Fix for SVG Stacks in background

  $.fn.fixSVGStackBackground = function() {

    this.each(function() {

      var $el = $(this);

      // At the heart of the bug:
      // Both jquery's `$el.css('background-image')` and `getComputedStyle($el[0], null).backgroundImage`
      // return and url without the #target part;

      var url = $el[0].style.backgroundImage.slice(4, (- 1)).replace(/["']/g, '');

      // Here is the quick and dirty hack, if enabled

      if(USE_DIRTY_CSS_CONTENT_HACK) {
        // Read url form `style.content`, the css content property is used to transport the information
        var style = getComputedStyle($el[0], null);
        if(style.backgroundImage.indexOf('.svg') !== -1 && style.content.indexOf('.svg#') !== -1) {
          url = style.content.replace(/["']/g, '');
        }
      }

      if(url.indexOf('.svg#') === -1) {
        return;
      }

      getdataURIFromStack(url, function(dataURI) {
        // Replace background-image url with dataURI
        $el.css('background-image', 'url(' + dataURI + ')');
      });

    });
    return this;
  };

  // Fix for SVG Stacks in img Tags

  $.fn.fixSVGStack = function() {
    this.each(function() {

      var $el = $(this);
      var url = $el.attr('src');

      if(url.indexOf('.svg#') === -1) {
        return;
      }
      getdataURIFromStack(url, function(dataURI) {
        // Replace src with dataURI
        $el.attr('src', dataURI);
      });
    });
    return this;
  };

})(jQuery);<|endoftext|>