<commit_before>'use strict';
/*jshint laxbreak:true, loopfunc:true*/

var path = require('path')
  , Module = require('module')
  , resolve = require('resolve')
  , dirname = require('path').dirname
  , ProxyquireError = require('./proxyquire-error')
  , is = require('./is')
  , assert = require('assert')
  , fillMissingKeys = require('fill-keys')
  , moduleNotFoundError = require('module-not-found-error')
  , hasOwnProperty = Object.prototype.hasOwnProperty
  ;

function validateArguments(request, stubs) {
  var msg = (function getMessage() {
    if (!request)
      return 'Missing argument: "request". Need it to resolve desired module.';

    if (!stubs)
      return 'Missing argument: "stubs". If no stubbing is needed, use regular require instead.';

    if (!is.String(request))
      return 'Invalid argument: "request". Needs to be a requirable string that is the module to load.';

    if (!is.Object(stubs))
      return 'Invalid argument: "stubs". Needs to be an object containing overrides e.g., {"path": { extname: function () { ... } } }.';
  })();

  if (msg) throw new ProxyquireError(msg);
}

function Proxyquire(parent) {
  var self = this
    , fn = self.load.bind(self)
    , proto = Proxyquire.prototype
    ;

  this._parent = parent;
  this._preserveCache = true;

  Object.keys(proto)
    .forEach(function (key) {
      if (is.Function(proto[key])) fn[key] = proto[key].bind(self);
    });

  self.fn = fn;
  return fn;
}

/**
 * Disables call thru, which determines if keys of original modules will be used
 * when they weren't stubbed out.
 * @name noCallThru
 * @function
 * @private
 * @return {object} The proxyquire function to allow chaining
 */
Proxyquire.prototype.noCallThru = function () {
  this._noCallThru = true;
  return this.fn;
};

/**
 * Enables call thru, which determines if keys of original modules will be used
 * when they weren't stubbed out.
 * @name callThru
 * @function
 * @private
 * @return {object} The proxyquire function to allow chaining
 */
Proxyquire.prototype.callThru = function () {
  this._noCallThru = false;
  return this.fn;
};

/**
 * Will make proxyquire remove the requested modules from the `require.cache` in order to force
 * them to be reloaded the next time they are proxyquired.
 * This behavior differs from the way nodejs `require` works, but for some tests this maybe useful.
 *
 * @name noPreserveCache
 * @function
 * @private
 * @return {object} The proxyquire function to allow chaining
 */
Proxyquire.prototype.noPreserveCache = function() {
  this._preserveCache = false;
  return this.fn;
};

/**
 * Restores proxyquire caching behavior to match the one of nodejs `require`
 *
 * @name preserveCache
 * @function
 * @private
 * @return {object} The proxyquire function to allow chaining
 */
Proxyquire.prototype.preserveCache = function() {
  this._preserveCache = true;
  return this.fn;
};

/**
 * Loads a module using the given stubs instead of their normally resolved required modules.
 * @param request The requirable module path to load.
 * @param stubs The stubs to use. e.g., { "path": { extname: function () { ... } } }
 * @return {*} A newly resolved module with the given stubs.
 */
Proxyquire.prototype.load = function (request, stubs) {
  validateArguments(request, stubs);

  // Find out if any of the passed stubs are global overrides
  for (var key in stubs) {
    var stub = stubs[key];

    if (stub === null) continue;

    if (typeof stub === 'undefined') {
      throw new ProxyquireError('Invalid stub: "' + key + '" cannot be undefined');
    }

    if (hasOwnProperty.call(stub, '@global')) {
      this._containsGlobal = true;
    }

    if (hasOwnProperty.call(stub, '@runtimeGlobal')) {
      this._containsGlobal = true;
      this._containsRuntimeGlobal = true;
    }
  }

  // Ignore the module cache when return the requested module
  return this._withoutCache(this._parent, stubs, request, this._parent.require.bind(this._parent, request));
};

// This replaces a module's require function
Proxyquire.prototype._require = function(module, stubs, path) {
  assert(typeof path === 'string', 'path must be a string');
  assert(path, 'missing path');

  if (hasOwnProperty.call(stubs, path)) {
    var stub = stubs[path];

    if (stub === null) {
      // Mimic the module-not-found exception thrown by node.js.
      throw moduleNotFoundError(path);
    }

    if (hasOwnProperty.call(stub, '@noCallThru') ? !stub['@noCallThru'] : !this._noCallThru) {
      fillMissingKeys(stub, Module._load(path, module));
    }

    // We are top level or this stub is marked as global
    if (module.parent == this._parent || hasOwnProperty.call(stub, '@global') || hasOwnProperty.call(stub, '@runtimeGlobal')) {
      return stub;
    }
  }

  // Only ignore the cache if we have global stubs
  if (this._containsRuntimeGlobal) {
    return this._withoutCache(module, stubs, path, Module._load.bind(Module, path, module));
  } else {
    return Module._load(path, module);
  }
};

Proxyquire.prototype._withoutCache = function(module, stubs, path, func) {
  // Temporarily disable the cache - either per-module or globally if we have global stubs
  var restoreCache = this._disableCache(module, path);

  // Override all require extension handlers
  var restoreExtensionHandlers = this._overrideExtensionHandlers(module, stubs);

  try {
    // Execute the function that needs the module cache disabled
    return func();
  } finally {
    // Restore the cache if we are preserving it
    if (this._preserveCache) {
      restoreCache();
    } else {
      var id = Module._resolveFilename(path, module);
      var stubIds = Object.keys(stubs).map(function (stubPath) {
        try {
          return resolve.sync(stubPath, {
            basedir: dirname(id),
            extensions: Object.keys(require.extensions),
            paths: Module.globalPaths
          })
        } catch (_) {}
      });
      var ids = [id].concat(stubIds.filter(Boolean));

      ids.forEach(function (id) {
        delete require.cache[id];
      });
    }

    // Finally restore the original extension handlers
    restoreExtensionHandlers();
  }
};

Proxyquire.prototype._disableCache = function(module, path) {
  if (this._containsGlobal) {
    // empty the require cache because if we are stubbing C but requiring A,
    // and if A requires B and B requires C, then B and C might be cached already
    // and we'll never get the chance to return our stub
    return this._disableGlobalCache();
  }

  // Temporarily delete the SUT from the require cache
  return this._disableModuleCache(path, module);
};

Proxyquire.prototype._disableGlobalCache = function() {
  var cache = require.cache;
  require.cache = Module._cache = {};

  for (var id in cache) {
    // Keep native modules (i.e. `.node` files).
    // Otherwise, Node.js would throw a “Module did not self-register”
    // error upon requiring it a second time.
    // See https://github.com/nodejs/node/issues/5016.
    if (/\.node$/.test(id)) {
      require.cache[id] = cache[id];
    }
  }

  // Return a function that will undo what we just did
  return function() {
    // Keep native modules which were added to the cache in the meantime.
    for (var id in require.cache) {
      if (/\.node$/.test(id)) {
        cache[id] = require.cache[id]
      }
    }

    require.cache = Module._cache = cache;
  };
};

Proxyquire.prototype._disableModuleCache = function(path, module) {
  // Find the ID (location) of the SUT, relative to the parent
  var id = Module._resolveFilename(path, module);

  var cached = Module._cache[id];
  delete Module._cache[id];

  // Return a function that will undo what we just did
  return function() {
    if (cached) {
      Module._cache[id] = cached;
    }
  };
};

Proxyquire.prototype._overrideExtensionHandlers = function(module, stubs) {
  var originalExtensions = {};
  var self = this;

  Object.keys(require.extensions).forEach(function(extension) {
    // Store the original so we can restore it later
    if (!originalExtensions[extension]) {
      originalExtensions[extension] = require.extensions[extension];
    }

    // Override the default handler for the requested file extension
    require.extensions[extension] = function(module, filename) {
      // Override the require method for this module
      module.require = self._require.bind(self, module, stubs);

      return originalExtensions[extension](module, filename);
    };
  });

  // Return a function that will undo what we just did
  return function() {
    Object.keys(originalExtensions).forEach(function(extension) {
      require.extensions[extension] = originalExtensions[extension];
    });
  };
};

module.exports = Proxyquire;
<commit_msg>Allow inheritance from proxyquire (#163)<commit_after>'use strict';
/*jshint laxbreak:true, loopfunc:true*/

var path = require('path')
  , Module = require('module')
  , resolve = require('resolve')
  , dirname = require('path').dirname
  , ProxyquireError = require('./proxyquire-error')
  , is = require('./is')
  , assert = require('assert')
  , fillMissingKeys = require('fill-keys')
  , moduleNotFoundError = require('module-not-found-error')
  , hasOwnProperty = Object.prototype.hasOwnProperty
  ;

function validateArguments(request, stubs) {
  var msg = (function getMessage() {
    if (!request)
      return 'Missing argument: "request". Need it to resolve desired module.';

    if (!stubs)
      return 'Missing argument: "stubs". If no stubbing is needed, use regular require instead.';

    if (!is.String(request))
      return 'Invalid argument: "request". Needs to be a requirable string that is the module to load.';

    if (!is.Object(stubs))
      return 'Invalid argument: "stubs". Needs to be an object containing overrides e.g., {"path": { extname: function () { ... } } }.';
  })();

  if (msg) throw new ProxyquireError(msg);
}

function Proxyquire(parent) {
  var self = this
    , fn = self.load.bind(self)
    , proto = Proxyquire.prototype
    ;

  this._parent = parent;
  this._preserveCache = true;

  Object.keys(proto)
    .forEach(function (key) {
      if (is.Function(proto[key])) fn[key] = self[key].bind(self);
    });

  self.fn = fn;
  return fn;
}

/**
 * Disables call thru, which determines if keys of original modules will be used
 * when they weren't stubbed out.
 * @name noCallThru
 * @function
 * @private
 * @return {object} The proxyquire function to allow chaining
 */
Proxyquire.prototype.noCallThru = function () {
  this._noCallThru = true;
  return this.fn;
};

/**
 * Enables call thru, which determines if keys of original modules will be used
 * when they weren't stubbed out.
 * @name callThru
 * @function
 * @private
 * @return {object} The proxyquire function to allow chaining
 */
Proxyquire.prototype.callThru = function () {
  this._noCallThru = false;
  return this.fn;
};

/**
 * Will make proxyquire remove the requested modules from the `require.cache` in order to force
 * them to be reloaded the next time they are proxyquired.
 * This behavior differs from the way nodejs `require` works, but for some tests this maybe useful.
 *
 * @name noPreserveCache
 * @function
 * @private
 * @return {object} The proxyquire function to allow chaining
 */
Proxyquire.prototype.noPreserveCache = function() {
  this._preserveCache = false;
  return this.fn;
};

/**
 * Restores proxyquire caching behavior to match the one of nodejs `require`
 *
 * @name preserveCache
 * @function
 * @private
 * @return {object} The proxyquire function to allow chaining
 */
Proxyquire.prototype.preserveCache = function() {
  this._preserveCache = true;
  return this.fn;
};

/**
 * Loads a module using the given stubs instead of their normally resolved required modules.
 * @param request The requirable module path to load.
 * @param stubs The stubs to use. e.g., { "path": { extname: function () { ... } } }
 * @return {*} A newly resolved module with the given stubs.
 */
Proxyquire.prototype.load = function (request, stubs) {
  validateArguments(request, stubs);

  // Find out if any of the passed stubs are global overrides
  for (var key in stubs) {
    var stub = stubs[key];

    if (stub === null) continue;

    if (typeof stub === 'undefined') {
      throw new ProxyquireError('Invalid stub: "' + key + '" cannot be undefined');
    }

    if (hasOwnProperty.call(stub, '@global')) {
      this._containsGlobal = true;
    }

    if (hasOwnProperty.call(stub, '@runtimeGlobal')) {
      this._containsGlobal = true;
      this._containsRuntimeGlobal = true;
    }
  }

  // Ignore the module cache when return the requested module
  return this._withoutCache(this._parent, stubs, request, this._parent.require.bind(this._parent, request));
};

// This replaces a module's require function
Proxyquire.prototype._require = function(module, stubs, path) {
  assert(typeof path === 'string', 'path must be a string');
  assert(path, 'missing path');

  if (hasOwnProperty.call(stubs, path)) {
    var stub = stubs[path];

    if (stub === null) {
      // Mimic the module-not-found exception thrown by node.js.
      throw moduleNotFoundError(path);
    }

    if (hasOwnProperty.call(stub, '@noCallThru') ? !stub['@noCallThru'] : !this._noCallThru) {
      fillMissingKeys(stub, Module._load(path, module));
    }

    // We are top level or this stub is marked as global
    if (module.parent == this._parent || hasOwnProperty.call(stub, '@global') || hasOwnProperty.call(stub, '@runtimeGlobal')) {
      return stub;
    }
  }

  // Only ignore the cache if we have global stubs
  if (this._containsRuntimeGlobal) {
    return this._withoutCache(module, stubs, path, Module._load.bind(Module, path, module));
  } else {
    return Module._load(path, module);
  }
};

Proxyquire.prototype._withoutCache = function(module, stubs, path, func) {
  // Temporarily disable the cache - either per-module or globally if we have global stubs
  var restoreCache = this._disableCache(module, path);

  // Override all require extension handlers
  var restoreExtensionHandlers = this._overrideExtensionHandlers(module, stubs);

  try {
    // Execute the function that needs the module cache disabled
    return func();
  } finally {
    // Restore the cache if we are preserving it
    if (this._preserveCache) {
      restoreCache();
    } else {
      var id = Module._resolveFilename(path, module);
      var stubIds = Object.keys(stubs).map(function (stubPath) {
        try {
          return resolve.sync(stubPath, {
            basedir: dirname(id),
            extensions: Object.keys(require.extensions),
            paths: Module.globalPaths
          })
        } catch (_) {}
      });
      var ids = [id].concat(stubIds.filter(Boolean));

      ids.forEach(function (id) {
        delete require.cache[id];
      });
    }

    // Finally restore the original extension handlers
    restoreExtensionHandlers();
  }
};

Proxyquire.prototype._disableCache = function(module, path) {
  if (this._containsGlobal) {
    // empty the require cache because if we are stubbing C but requiring A,
    // and if A requires B and B requires C, then B and C might be cached already
    // and we'll never get the chance to return our stub
    return this._disableGlobalCache();
  }

  // Temporarily delete the SUT from the require cache
  return this._disableModuleCache(path, module);
};

Proxyquire.prototype._disableGlobalCache = function() {
  var cache = require.cache;
  require.cache = Module._cache = {};

  for (var id in cache) {
    // Keep native modules (i.e. `.node` files).
    // Otherwise, Node.js would throw a “Module did not self-register”
    // error upon requiring it a second time.
    // See https://github.com/nodejs/node/issues/5016.
    if (/\.node$/.test(id)) {
      require.cache[id] = cache[id];
    }
  }

  // Return a function that will undo what we just did
  return function() {
    // Keep native modules which were added to the cache in the meantime.
    for (var id in require.cache) {
      if (/\.node$/.test(id)) {
        cache[id] = require.cache[id]
      }
    }

    require.cache = Module._cache = cache;
  };
};

Proxyquire.prototype._disableModuleCache = function(path, module) {
  // Find the ID (location) of the SUT, relative to the parent
  var id = Module._resolveFilename(path, module);

  var cached = Module._cache[id];
  delete Module._cache[id];

  // Return a function that will undo what we just did
  return function() {
    if (cached) {
      Module._cache[id] = cached;
    }
  };
};

Proxyquire.prototype._overrideExtensionHandlers = function(module, stubs) {
  var originalExtensions = {};
  var self = this;

  Object.keys(require.extensions).forEach(function(extension) {
    // Store the original so we can restore it later
    if (!originalExtensions[extension]) {
      originalExtensions[extension] = require.extensions[extension];
    }

    // Override the default handler for the requested file extension
    require.extensions[extension] = function(module, filename) {
      // Override the require method for this module
      module.require = self._require.bind(self, module, stubs);

      return originalExtensions[extension](module, filename);
    };
  });

  // Return a function that will undo what we just did
  return function() {
    Object.keys(originalExtensions).forEach(function(extension) {
      require.extensions[extension] = originalExtensions[extension];
    });
  };
};

module.exports = Proxyquire;
<|endoftext|>


<commit_before>import querystring from 'querystring';
import crypto from 'crypto';
import EventTracker from 'event-tracker';
import omit from 'lodash/object/omit';
import constants from '../../src/constants';
import addIfPresent from '../../src/lib/addIfPresent';
import makeRequest from '../../src/lib/makeRequest';
import url from 'url';
import gtm from './gtm';

// Build a regex which can pull the base36 out of a prefixed or unprefixed id.
const idRegex = /(?:t\d+_)?(.*)/;

function calculateHash (key, string) {
  const hmac = crypto.createHmac('sha256', key);
  hmac.setEncoding('hex');
  hmac.write(string);
  hmac.end();

  return hmac.read();
}

function postData(eventInfo) {
  const { url, data, query, headers } = eventInfo;

  makeRequest
    .post(url)
    .set(headers)
    .query(query)
    .timeout(constants.DEFAULT_API_TIMEOUT)
    .send(data)
    .then();
}

function trackingEvents(app) {
  let trackerSecret = app.config.trackerClientSecret || '';
  trackerSecret = new Buffer(trackerSecret, 'base64').toString();
  app.eventQueue = [];

  const tracker = new EventTracker(
    app.config.trackerKey,
    trackerSecret,
    postData,
    app.config.trackerEndpoint,
    app.config.trackerClientAppName,
    calculateHash
  );

  function eventSend(topic, type, payload) {
    if (tracker) {
      if (app.getState('loaded')) {
        return tracker.track(topic, type, payload);
      }

      app.eventQueue.push([topic, type, payload]);
    }
  }

  function clearEventQueue() {
    if (app.eventQueue.length) {
      app.eventQueue.forEach((e) => {
        eventSend(...e);
      });
    }
  }

  function gaSend () {
    if (global && global.ga) {
      global.ga.apply(null, [...arguments]);
    }
  }

  function convertId (id) {
    const unprefixedId = idRegex.exec(id)[1];
    return parseInt(unprefixedId, 36);
  }

  function isOtherListing(props) {
    return props.ctx.params.subreddit || props.ctx.params.multi ||
      props.ctx.path === '/';
  }

  function buildPageviewData (props) {
    const LINK_LIMIT = 25;

    const data = {
      language: 'en', // NOTE: update when there are translations
    };

    if (props.ctx.referrer) {
      data.referrer_url = props.ctx.referrer;
      data.referrer_domain = url.parse(props.ctx.referrer).host;
    }

    data.dnt = !!window.DO_NOT_TRACK;

    // If there is a logged-in user, add the user's data to the payload
    if (props.data.user) {
      data.user_name = props.data.user.name;
      data.user_id = convertId(props.data.user.id);
    } else {
      // Otherwise, send in logged-out ID
      data.loid = props.loid;
      data.loid_created = props.loidcreated;
    }

    // If we're looking at a subreddit, include the info in the payload
    if (props.data.subreddit) {
      data.sr_id = convertId(props.data.subreddit.name);
      data.sr_name = props.data.subreddit.id;
    }

    // If we're looking at a list of links or comments, include the sort order
    // (or a default). If it's just a list of links, not comments, also include
    // the page size.
    if (props.data.listings || props.data.search || props.data.activities || props.data.comments) {
      if (props.ctx.query.sort === 'top') {
        data.target_filter_time = props.ctx.query.time || 'all';
      }

      if (props.data.comments || props.data.activities) {
        data.target_sort = props.ctx.query.sort || 'confidence';
      } else {
        data.target_sort = props.ctx.query.sort || 'hot';
        data.target_count = LINK_LIMIT;

        const query = props.ctx.query;
        if (query.before) {
          data.target_before = query.before;
        }

        if (query.after) {
          data.target_after = query.after;
        }
      }

      if (props.data.search && props.ctx.query.q) {
        data.query_string = props.ctx.query.q;
        data.query_string_length = props.ctx.query.q.length;
      }

      if (props.data.search) {
        data.sr_listing = props.data.search.subreddits.map(sr => sr.display_name).join(',');
      }
    }

    // Try looking at the data to determine what the subject of the page is.
    // In order of priority, it could be a user profile, a listing, or a
    // subreddit.
    const target = (
      props.data.userProfile ||
      props.data.listing ||
      props.data.subreddit
    );

    data.compact_view = props.compact;

    if (target) {
      // Subreddit ids/names are swapped
      if (props.ctx.params.commentId) {
        data.target_id = convertId(props.ctx.params.commentId);
        data.target_fullname = `t1_${props.ctx.params.commentId}`;
        data.target_type = 'comment';
      } else if (target._type === 'Subreddit') {
        data.target_id = convertId(target.name);
        data.target_fullname = `${target.name}`;
        data.target_type = 'listing';
        data.listing_name = target.id;
      } else if (target._type === 'Link') {
        data.target_id = convertId(target.id);
        data.target_fullname = `t3_${target.id}`;
        data.target_type = 'link';
        if (target.selftext) {
          data.target_type = 'self';
        }
      } else if (target._type === 'Account') {
        data.target_id = convertId(target.id);
        data.target_name = target.name;
        data.target_fullname = `t2_${target.id}`;
        data.target_type = 'account';
      }

      if (target._type === 'Link') {
        data.target_url = target.url;
        data.target_url_domain = target.domain;
      }
    } else if (isOtherListing(props)) {
      // Fake subreddit, mark it as a listing
      data.target_type = 'listing';

      // explicitly check that this is the frontpage
      if (props.ctx.path === '/') {
        data.listing_name = 'frontpage';
      } else if (props.ctx.params.subreddit) {
        const subreddit = props.ctx.params.subreddit;
        if (subreddit.indexOf('+') !== -1) {
          data.listing_name = 'multi';
        } else {
          data.listing_name = subreddit;
        }
      } else if (props.ctx.params.multi) {
        data.listing_name = 'multi';
      }
    }

    return data;
  }

  function getBasePayload(props) {
    return {
      domain: window.location.host,
      geoip_country: props.country || null,
      user_agent: props.ctx.userAgent,
    };
  }

  function buildLoginData(props) {
    const payload = {
      ...getBasePayload(props),
      successful: props.successful,
      user_name: props.user.name,
    };

    addIfPresent(payload, 'user_id36', props.user.id);
    addIfPresent(payload, 'loid', props.loid);
    addIfPresent(payload, 'loid_created', props.loidcreated);
    addIfPresent(payload, 'process_notes', props.process_notes);
    addIfPresent(payload, 'email', props.email);
    addIfPresent(payload, 'email_verified', props.has_verified_email);

    // originalUrl can only be a relative url
    if (props.originalUrl) {
      payload.referrer_domain = payload.domain;
      payload.referrer_url = payload.domain + props.originalUrl;
    } else if (props.ctx.referrer) {
      payload.referrer_domain = url.parse(props.ctx.referrer).host;
      payload.referrer_url = props.ctx.referrer;
    }

    return payload;
  }

  function handleLogin(props) {
    const { data, ctx, app } = props;
    if (data.user) {
      const notifications = ctx.notifications || [];

      const loginAction = notifications.find((v) => {
        return v === 'login' || v === 'register';
      });

      if (loginAction) {
        const eventProps = {
          ...props,
          user: data.user,
          successful: true,
          country: app.getState('country'),
          // We get redirected to the referrer (in app only) after
          // successful login so we know it was the current route.
          originalUrl: ctx.path,
        };

        app.emit(`${loginAction}:attempt`, eventProps);
      }
    }
  }

  app.on('pageview', function(props) {
    const payload = buildPageviewData(props);
    app.setState('loaded', true);
    eventSend('screenview_events', 'cs.screenview', payload);
    handleLogin(props);
    clearEventQueue();
    gtm.trigger('pageview', { subreddit: props.ctx.params.subreddit || '' });
  });

  app.on('route:start', function(ctx) {
    // app 'loaded' state is used to check if we should queue all events other than
    // pageview to ensure pageview is first
    app.setState('loaded', false);

    const query = querystring.stringify(ctx.query);
    let fullUrl = ctx.path;

    if (query) {
      fullUrl += `?${query}`;
    }

    gaSend('set', 'page', fullUrl);
    gaSend('send', 'pageview');

    const loggedIn = !!window.bootstrap.user;

    const compactCookieValue = document.cookie.match(/\bcompact=(\w+)\b/);
    const compact = !!(compactCookieValue &&
                    compactCookieValue.length > 1 &&
                    compactCookieValue[1] === 'true');

    const compactTestCookieValue = document.cookie.match(/\bcompactTest=(\w+)\b/);
    const compactTest = compactTestCookieValue ? compactTestCookieValue[1] : 'undefined';

    gaSend('set', 'dimension2', loggedIn.toString());
    gaSend('set', 'dimension3', compact.toString());
    gaSend('set', 'dimension4', compactTest.toString());
  });

  app.on('compactToggle', function (compact) {
    gaSend('send', 'event', 'compactToggle', compact.toString());
    gaSend('set', 'dimension3', compact.toString());
  });

  app.on('login:attempt', function(props) {
    const payload = buildLoginData(props);
    eventSend('login_events', 'cs.login_attempt', payload);
    gaSend('send', 'event', 'login', 'attempt');
  });

  app.on('register:attempt', function(props) {
    const payload = buildLoginData(props);
    eventSend('login_events', 'cs.register_attempt', payload);
    gaSend('send', 'event', 'register', 'attempt');
  });

  app.on('vote', function(vote) {
    gaSend('send', 'event', 'vote', vote.get('direction'));
  });

  app.on('comment', function(comment) {
    if (comment.text) {
      gaSend('send', 'event', 'comment', 'words', comment.text.match(/\S+/g).length);
    }
  });

  app.on('comment:edit', function() {
    gaSend('send', 'event', 'comment', 'edit');
  });

  app.on('search', function() {
    gaSend('send', 'event', 'search');
  });

  app.on('goto', function(query) {
    gaSend('send', 'event', 'goto', query);
  });

  app.on('report', function() {
    gaSend('send', 'event', 'report');
  });

  app.on('post:submit', function(subreddit) {
    gaSend('send', 'event', 'post', 'submit', subreddit);
  });

  app.on('post:edit', function() {
    gaSend('send', 'event', 'post', 'edit');
  });

  app.on('post:selectSubreddit', function(subreddit) {
    gaSend('send', 'event', 'post', 'selectSubreddit', subreddit);
  });

  app.on('post:error', function() {
    gaSend('send', 'event', 'post', 'captcha');
  });

  app.on('message:submit', function() {
    gaSend('send', 'event', 'messages', 'submit');
  });

  app.on('message:reply', function(message) {
    if (message.text) {
      gaSend('send', 'event', 'messages', 'reply', message.text.match(/\S+/g).length);
    }
  });

  app.on('searchBar', function(payload) {
    eventSend('search_events', payload.type, omit(payload, 'type'));
  });
}

export default trackingEvents;
<commit_msg>Update search events<commit_after>import querystring from 'querystring';
import crypto from 'crypto';
import EventTracker from 'event-tracker';
import omit from 'lodash/object/omit';
import constants from '../../src/constants';
import addIfPresent from '../../src/lib/addIfPresent';
import makeRequest from '../../src/lib/makeRequest';
import url from 'url';
import gtm from './gtm';

// Build a regex which can pull the base36 out of a prefixed or unprefixed id.
const idRegex = /(?:t\d+_)?(.*)/;

function calculateHash (key, string) {
  const hmac = crypto.createHmac('sha256', key);
  hmac.setEncoding('hex');
  hmac.write(string);
  hmac.end();

  return hmac.read();
}

function postData(eventInfo) {
  const { url, data, query, headers } = eventInfo;

  makeRequest
    .post(url)
    .set(headers)
    .query(query)
    .timeout(constants.DEFAULT_API_TIMEOUT)
    .send(data)
    .then();
}

function trackingEvents(app) {
  let trackerSecret = app.config.trackerClientSecret || '';
  trackerSecret = new Buffer(trackerSecret, 'base64').toString();
  app.eventQueue = [];

  const tracker = new EventTracker(
    app.config.trackerKey,
    trackerSecret,
    postData,
    app.config.trackerEndpoint,
    app.config.trackerClientAppName,
    calculateHash
  );

  function eventSend(topic, type, payload) {
    if (tracker) {
      if (app.getState('loaded')) {
        return tracker.track(topic, type, payload);
      }

      app.eventQueue.push([topic, type, payload]);
    }
  }

  function clearEventQueue() {
    if (app.eventQueue.length) {
      app.eventQueue.forEach((e) => {
        eventSend(...e);
      });
    }
  }

  function gaSend () {
    if (global && global.ga) {
      global.ga.apply(null, [...arguments]);
    }
  }

  function convertId (id) {
    const unprefixedId = idRegex.exec(id)[1];
    return parseInt(unprefixedId, 36);
  }

  function isOtherListing(props) {
    return props.ctx.params.subreddit || props.ctx.params.multi ||
      props.ctx.path === '/';
  }

  function buildPageviewData (props) {
    const LINK_LIMIT = 25;

    const data = {
      language: 'en', // NOTE: update when there are translations
    };

    if (props.ctx.referrer) {
      data.referrer_url = props.ctx.referrer;
      data.referrer_domain = url.parse(props.ctx.referrer).host;
    }

    data.dnt = !!window.DO_NOT_TRACK;

    // If there is a logged-in user, add the user's data to the payload
    if (props.data.user) {
      data.user_name = props.data.user.name;
      data.user_id = convertId(props.data.user.id);
    } else {
      // Otherwise, send in logged-out ID
      data.loid = props.loid;
      data.loid_created = props.loidcreated;
    }

    // If we're looking at a subreddit, include the info in the payload
    if (props.data.subreddit) {
      data.sr_id = convertId(props.data.subreddit.name);
      data.sr_name = props.data.subreddit.id;
    }

    // If we're looking at a list of links or comments, include the sort order
    // (or a default). If it's just a list of links, not comments, also include
    // the page size.
    if (props.data.listings || props.data.search || props.data.activities || props.data.comments) {
      if (props.ctx.query.sort === 'top') {
        data.target_filter_time = props.ctx.query.time || 'all';
      }

      if (props.data.comments || props.data.activities) {
        data.target_sort = props.ctx.query.sort || 'confidence';
      } else {
        data.target_sort = props.ctx.query.sort || 'hot';
        data.target_count = LINK_LIMIT;

        const query = props.ctx.query;
        if (query.before) {
          data.target_before = query.before;
        }

        if (query.after) {
          data.target_after = query.after;
        }
      }

      if (props.data.search && props.ctx.query.q) {
        data.query_string = props.ctx.query.q;
        data.query_string_length = props.ctx.query.q.length;
      }

      if (props.data.search) {
        data.sr_listing = props.data.search.subreddits.map(sr => sr.display_name);
        data.target_type = 'search_results';
      }
    }

    // Try looking at the data to determine what the subject of the page is.
    // In order of priority, it could be a user profile, a listing, or a
    // subreddit.
    const target = (
      props.data.userProfile ||
      props.data.listing ||
      props.data.subreddit
    );

    data.compact_view = props.compact;

    if (target) {
      // Subreddit ids/names are swapped
      if (props.ctx.params.commentId) {
        data.target_id = convertId(props.ctx.params.commentId);
        data.target_fullname = `t1_${props.ctx.params.commentId}`;
        data.target_type = 'comment';
      } else if (target._type === 'Subreddit') {
        data.target_id = convertId(target.name);
        data.target_fullname = `${target.name}`;
        data.target_type = 'listing';
        data.listing_name = target.id;
      } else if (target._type === 'Link') {
        data.target_id = convertId(target.id);
        data.target_fullname = `t3_${target.id}`;
        data.target_type = 'link';
        if (target.selftext) {
          data.target_type = 'self';
        }
      } else if (target._type === 'Account') {
        data.target_id = convertId(target.id);
        data.target_name = target.name;
        data.target_fullname = `t2_${target.id}`;
        data.target_type = 'account';
      }

      if (target._type === 'Link') {
        data.target_url = target.url;
        data.target_url_domain = target.domain;
      }
    } else if (isOtherListing(props)) {
      // Fake subreddit, mark it as a listing
      data.target_type = 'listing';

      // explicitly check that this is the frontpage
      if (props.ctx.path === '/') {
        data.listing_name = 'frontpage';
      } else if (props.ctx.params.subreddit) {
        const subreddit = props.ctx.params.subreddit;
        if (subreddit.indexOf('+') !== -1) {
          data.listing_name = 'multi';
        } else {
          data.listing_name = subreddit;
        }
      } else if (props.ctx.params.multi) {
        data.listing_name = 'multi';
      }
    }

    return data;
  }

  function getBasePayload(props) {
    return {
      domain: window.location.host,
      geoip_country: props.country || null,
      user_agent: props.ctx.userAgent,
    };
  }

  function buildLoginData(props) {
    const payload = {
      ...getBasePayload(props),
      successful: props.successful,
      user_name: props.user.name,
    };

    addIfPresent(payload, 'user_id36', props.user.id);
    addIfPresent(payload, 'loid', props.loid);
    addIfPresent(payload, 'loid_created', props.loidcreated);
    addIfPresent(payload, 'process_notes', props.process_notes);
    addIfPresent(payload, 'email', props.email);
    addIfPresent(payload, 'email_verified', props.has_verified_email);

    // originalUrl can only be a relative url
    if (props.originalUrl) {
      payload.referrer_domain = payload.domain;
      payload.referrer_url = payload.domain + props.originalUrl;
    } else if (props.ctx.referrer) {
      payload.referrer_domain = url.parse(props.ctx.referrer).host;
      payload.referrer_url = props.ctx.referrer;
    }

    return payload;
  }

  function handleLogin(props) {
    const { data, ctx, app } = props;
    if (data.user) {
      const notifications = ctx.notifications || [];

      const loginAction = notifications.find((v) => {
        return v === 'login' || v === 'register';
      });

      if (loginAction) {
        const eventProps = {
          ...props,
          user: data.user,
          successful: true,
          country: app.getState('country'),
          // We get redirected to the referrer (in app only) after
          // successful login so we know it was the current route.
          originalUrl: ctx.path,
        };

        app.emit(`${loginAction}:attempt`, eventProps);
      }
    }
  }

  app.on('pageview', function(props) {
    const payload = buildPageviewData(props);
    app.setState('loaded', true);
    eventSend('screenview_events', 'cs.screenview', payload);
    handleLogin(props);
    clearEventQueue();
    gtm.trigger('pageview', { subreddit: props.ctx.params.subreddit || '' });
  });

  app.on('route:start', function(ctx) {
    // app 'loaded' state is used to check if we should queue all events other than
    // pageview to ensure pageview is first
    app.setState('loaded', false);

    const query = querystring.stringify(ctx.query);
    let fullUrl = ctx.path;

    if (query) {
      fullUrl += `?${query}`;
    }

    gaSend('set', 'page', fullUrl);
    gaSend('send', 'pageview');

    const loggedIn = !!window.bootstrap.user;

    const compactCookieValue = document.cookie.match(/\bcompact=(\w+)\b/);
    const compact = !!(compactCookieValue &&
                    compactCookieValue.length > 1 &&
                    compactCookieValue[1] === 'true');

    const compactTestCookieValue = document.cookie.match(/\bcompactTest=(\w+)\b/);
    const compactTest = compactTestCookieValue ? compactTestCookieValue[1] : 'undefined';

    gaSend('set', 'dimension2', loggedIn.toString());
    gaSend('set', 'dimension3', compact.toString());
    gaSend('set', 'dimension4', compactTest.toString());
  });

  app.on('compactToggle', function (compact) {
    gaSend('send', 'event', 'compactToggle', compact.toString());
    gaSend('set', 'dimension3', compact.toString());
  });

  app.on('login:attempt', function(props) {
    const payload = buildLoginData(props);
    eventSend('login_events', 'cs.login_attempt', payload);
    gaSend('send', 'event', 'login', 'attempt');
  });

  app.on('register:attempt', function(props) {
    const payload = buildLoginData(props);
    eventSend('login_events', 'cs.register_attempt', payload);
    gaSend('send', 'event', 'register', 'attempt');
  });

  app.on('vote', function(vote) {
    gaSend('send', 'event', 'vote', vote.get('direction'));
  });

  app.on('comment', function(comment) {
    if (comment.text) {
      gaSend('send', 'event', 'comment', 'words', comment.text.match(/\S+/g).length);
    }
  });

  app.on('comment:edit', function() {
    gaSend('send', 'event', 'comment', 'edit');
  });

  app.on('search', function() {
    gaSend('send', 'event', 'search');
  });

  app.on('goto', function(query) {
    gaSend('send', 'event', 'goto', query);
  });

  app.on('report', function() {
    gaSend('send', 'event', 'report');
  });

  app.on('post:submit', function(subreddit) {
    gaSend('send', 'event', 'post', 'submit', subreddit);
  });

  app.on('post:edit', function() {
    gaSend('send', 'event', 'post', 'edit');
  });

  app.on('post:selectSubreddit', function(subreddit) {
    gaSend('send', 'event', 'post', 'selectSubreddit', subreddit);
  });

  app.on('post:error', function() {
    gaSend('send', 'event', 'post', 'captcha');
  });

  app.on('message:submit', function() {
    gaSend('send', 'event', 'messages', 'submit');
  });

  app.on('message:reply', function(message) {
    if (message.text) {
      gaSend('send', 'event', 'messages', 'reply', message.text.match(/\S+/g).length);
    }
  });

  app.on('searchBar', function(payload) {
    eventSend('search_events', payload.type, omit(payload, 'type'));
  });
}

export default trackingEvents;
<|endoftext|>


<commit_before>var path = require("path");
var WebpackConfig = require('webpack-config');

// Karma configuration here
module.exports = function (config) {
  config.set({
    port: 3334,
    browsers: ['PhantomJS'],
    // singleRun: true, //just run once by default
    frameworks: ['jasmine'], //use jasmine as framework
    files: [
      path.join(__dirname, 'node_modules', 'phantomjs-polyfill', 'bind-polyfill.js'), // To enable PhantomJS to render React components
      'webpack.tests.js' //test files
    ],
    preprocessors: {
      'webpack.tests.js': ['webpack', 'sourcemap']
    },
    reporters: ['progress'], //report results in this format

    junitReporter: {
      outputDir: '$CIRCLE_TEST_REPORTS', // results will be saved as $outputDir/$browserName.xml
      outputFile: undefined, // if included, results will be saved as $outputDir/$browserName/$outputFile
      suite: '', // suite will become the package name attribute in xml testsuite element
      useBrowserName: true // add browser name to report and classes names
    },

    webpack: new WebpackConfig().extend("webpack.test.config")
  });
};
<commit_msg>specify test type via dir name<commit_after>var path = require("path");
var WebpackConfig = require('webpack-config');

// Karma configuration here
module.exports = function (config) {
  config.set({
    port: 3334,
    browsers: ['PhantomJS'],
    // singleRun: true, //just run once by default
    frameworks: ['jasmine'], //use jasmine as framework
    files: [
      path.join(__dirname, 'node_modules', 'phantomjs-polyfill', 'bind-polyfill.js'), // To enable PhantomJS to render React components
      'webpack.tests.js' //test files
    ],
    preprocessors: {
      'webpack.tests.js': ['webpack', 'sourcemap']
    },
    reporters: ['progress'], //report results in this format

    junitReporter: {
      outputDir: '$CIRCLE_TEST_REPORTS/junit', // results will be saved as $outputDir/$browserName.xml
      outputFile: undefined, // if included, results will be saved as $outputDir/$browserName/$outputFile
      suite: '', // suite will become the package name attribute in xml testsuite element
      useBrowserName: true // add browser name to report and classes names
    },

    webpack: new WebpackConfig().extend("webpack.test.config")
  });
};
<|endoftext|>


<commit_before>'use strict';
var argscheck = require('cordova/argscheck'),
    utils = require('cordova/utils'),
    exec = require('cordova/exec');

var Media = function (){
    if(!mediaObjects){
        this.id = utils.createUUID();
        mediaObjects = {};
        mediaObjects[this.id] = this;
        this._mediaEventCallBack = {};
        this._containerElem = -1;
        this._duration = -1;
        this._position = -1;
        this.hooks = {};
        exec(null, null, 'toast.media', 'create',[this.id]);
    }
    else {
        throw new RangeError('Media instance exists already. toast Media supported single instance');
    }
};

var mediaObjects = null;
Media.getInstance = function() {
    if(mediaObjects && typeof mediaObjects == 'object'){
        for(var key in mediaObjects){
            if (mediaObjects.hasOwnProperty(key)){
                return mediaObjects[key];
            }
        }
    }
    else {
        return new Media();
    }
};

// Media EventType
Media.EVENT_STATE = 'STATE';
Media.EVENT_DURATION = 'DURATION';
Media.EVENT_POSITION = 'POSITION';
Media.EVENT_BUFFERINGPROGRESS = 'BUFFERINGPROGRESS';

//Media.MEDIA_SUBTITLE = 5;

// Media states
Media.STATE_IDLE = 'IDLE';
Media.STATE_PLAYING = 'PLAYING';
Media.STATE_PAUSED = 'PAUSED';
Media.STATE_STALLED = 'STALLED';
Media.STATE_SEEK = 'SEEK';

Media._MEDIA_CONTAINER = 'CONTAINER';
Media._MEDIA_ERROR = 'ERROR';

Media.prototype.registerHook = function (hook, fn) {
    this.hooks[hook] = this.hooks[hook] || [];
    this.hooks[hook].push(fn);
};
Media.prototype.unregisterHook = function (hook, fn) {
    if(!this.hooks[hook]) {
        return;
    }
    for(var i=this.hooks[hook].length-1; i>=0; i--) {
        if(this.hooks[hook][i] === fn) {
            this.hooks[hook].splice(i, 1);
        }
    }
};
Media.prototype.attachPlugin = function (plugin) {
    if(plugin.onAttachToMedia) {
        plugin.onAttachToMedia(this);
    }
};
function invokeHooks (hook, args) {
    var media = args[0];
    args = args.slice(1);
    if(!media.hooks[hook]) {
        return;
    }
    for(var i=0; i<media.hooks[hook].length; i++) {
        media.hooks[hook][i](media, args);
    }
}

Media.prototype.open = function(mediaUrl) {
    argscheck.checkArgs('s', 'Media.open', arguments);
    this.src = mediaUrl;
    invokeHooks('beforeopen', [this].concat(arguments));
    exec(null, null, 'toast.media', 'open', [this.id,this.src]);
    invokeHooks('afteropen', [this].concat(arguments));
};

Media.prototype.getContainerElement = function() {
    return this._containerElem;
};

Media.prototype.play = function(){
    invokeHooks('beforeplay', [this].concat(arguments));
    exec(null, null, 'toast.media', 'play', [this.id]);
    invokeHooks('afterplay', [this].concat(arguments));
};

Media.prototype.stop = function() {
    var me = this;
    exec(function() {
        me._position = -1;
        me._duration = -1;
    }, null, 'toast.media', 'stop', [this.id]);
};

Media.prototype.seekTo = function(milliseconds) {
    var me = this;
    exec(function(p) {
        me._position = p;
    }, null, 'toast.media', 'seekTo', [this.id, milliseconds]);
};

Media.prototype.pause = function() {
    exec(null, null, 'toast.media', 'pause', [this.id]);
};

Media.prototype.getDuration = function() {
    return this._duration;
};

Media.prototype.getCurrentPosition = function() {
    return this._position;
};

Media.prototype.setListener = function(listener) {
    argscheck.checkArgs('o', 'Media.setListener', arguments);
    if(arguments[0].onevent && typeof arguments[0].onevent !== 'function') {
        throw new TypeError('Type of listener.onevnet is not function');
    }
    if(arguments[0].onerror && typeof arguments[0].onerror !== 'function') {
        throw new TypeError('Type of listener.onerror is not function');
    }
    mediaObjects[this.id]._mediaEventCallBack = listener;
};

Media.prototype.unsetListener = function() {
     mediaObjects[this.id]._mediaEventCallBack = {};
};

Media.mediaEvent = function(id, value) {
    var media = mediaObjects[id];
    if(media) {
        switch(value.type) {
        case Media.EVENT_STATE :
            media._mediaEventCallBack.onevent && media._mediaEventCallBack.onevent(value);
            break;
        case Media.EVENT_DURATION :
            media._duration = value.data.duration;
            media._mediaEventCallBack.onevent && media._mediaEventCallBack.onevent(value);
            break;
        case Media.EVENT_POSITION :
            media._position = Number(value.data.position);
            media._mediaEventCallBack.onevent && media._mediaEventCallBack.onevent(value);
            break;
        case Media.EVENT_BUFFERINGPROGRESS :
            media._mediaEventCallBack.onevent && media._mediaEventCallBack.onevent(value);
            break;
        case Media._MEDIA_CONTAINER :
            media._containerElem = value.data.containerElem;
            break;
        case Media._MEDIA_ERROR :
            media._mediaEventCallBack.onerror && media._mediaEventCallBack.onerror(value);
            break;
        default :
            console.log('Unhandled Media.mediaEvent :: ' + value.type);
            break;
        }
    }
    else {
         console.log('Received Media.onStatus callback for unknown media:: ' + id);
    }
};

module.exports = Media;
<commit_msg>rename media to Media<commit_after>'use strict';
var argscheck = require('cordova/argscheck'),
    utils = require('cordova/utils'),
    exec = require('cordova/exec');

var Media = function (){
    if(!mediaObjects){
        this.id = utils.createUUID();
        mediaObjects = {};
        mediaObjects[this.id] = this;
        this._mediaEventCallBack = {};
        this._containerElem = -1;
        this._duration = -1;
        this._position = -1;
        this.hooks = {};
        exec(null, null, 'toast.Media', 'create',[this.id]);
    }
    else {
        throw new RangeError('Media instance exists already. toast Media supported single instance');
    }
};

var mediaObjects = null;
Media.getInstance = function() {
    if(mediaObjects && typeof mediaObjects == 'object'){
        for(var key in mediaObjects){
            if (mediaObjects.hasOwnProperty(key)){
                return mediaObjects[key];
            }
        }
    }
    else {
        return new Media();
    }
};

// Media EventType
Media.EVENT_STATE = 'STATE';
Media.EVENT_DURATION = 'DURATION';
Media.EVENT_POSITION = 'POSITION';
Media.EVENT_BUFFERINGPROGRESS = 'BUFFERINGPROGRESS';

//Media.MEDIA_SUBTITLE = 5;

// Media states
Media.STATE_IDLE = 'IDLE';
Media.STATE_PLAYING = 'PLAYING';
Media.STATE_PAUSED = 'PAUSED';
Media.STATE_STALLED = 'STALLED';
Media.STATE_SEEK = 'SEEK';

Media._MEDIA_CONTAINER = 'CONTAINER';
Media._MEDIA_ERROR = 'ERROR';

Media.prototype.registerHook = function (hook, fn) {
    this.hooks[hook] = this.hooks[hook] || [];
    this.hooks[hook].push(fn);
};
Media.prototype.unregisterHook = function (hook, fn) {
    if(!this.hooks[hook]) {
        return;
    }
    for(var i=this.hooks[hook].length-1; i>=0; i--) {
        if(this.hooks[hook][i] === fn) {
            this.hooks[hook].splice(i, 1);
        }
    }
};
Media.prototype.attachPlugin = function (plugin) {
    if(plugin.onAttachToMedia) {
        plugin.onAttachToMedia(this);
    }
};
function invokeHooks (hook, args) {
    var media = args[0];
    args = args.slice(1);
    if(!media.hooks[hook]) {
        return;
    }
    for(var i=0; i<media.hooks[hook].length; i++) {
        media.hooks[hook][i](media, args);
    }
}

Media.prototype.open = function(mediaUrl) {
    argscheck.checkArgs('s', 'Media.open', arguments);
    this.src = mediaUrl;
    invokeHooks('beforeopen', [this].concat(arguments));
    exec(null, null, 'toast.Media', 'open', [this.id,this.src]);
    invokeHooks('afteropen', [this].concat(arguments));
};

Media.prototype.getContainerElement = function() {
    return this._containerElem;
};

Media.prototype.play = function(){
    invokeHooks('beforeplay', [this].concat(arguments));
    exec(null, null, 'toast.Media', 'play', [this.id]);
    invokeHooks('afterplay', [this].concat(arguments));
};

Media.prototype.stop = function() {
    var me = this;
    exec(function() {
        me._position = -1;
        me._duration = -1;
    }, null, 'toast.Media', 'stop', [this.id]);
};

Media.prototype.seekTo = function(milliseconds) {
    var me = this;
    exec(function(p) {
        me._position = p;
    }, null, 'toast.Media', 'seekTo', [this.id, milliseconds]);
};

Media.prototype.pause = function() {
    exec(null, null, 'toast.Media', 'pause', [this.id]);
};

Media.prototype.getDuration = function() {
    return this._duration;
};

Media.prototype.getCurrentPosition = function() {
    return this._position;
};

Media.prototype.setListener = function(listener) {
    argscheck.checkArgs('o', 'Media.setListener', arguments);
    if(arguments[0].onevent && typeof arguments[0].onevent !== 'function') {
        throw new TypeError('Type of listener.onevnet is not function');
    }
    if(arguments[0].onerror && typeof arguments[0].onerror !== 'function') {
        throw new TypeError('Type of listener.onerror is not function');
    }
    mediaObjects[this.id]._mediaEventCallBack = listener;
};

Media.prototype.unsetListener = function() {
     mediaObjects[this.id]._mediaEventCallBack = {};
};

Media.mediaEvent = function(id, value) {
    var media = mediaObjects[id];
    if(media) {
        switch(value.type) {
        case Media.EVENT_STATE :
            media._mediaEventCallBack.onevent && media._mediaEventCallBack.onevent(value);
            break;
        case Media.EVENT_DURATION :
            media._duration = value.data.duration;
            media._mediaEventCallBack.onevent && media._mediaEventCallBack.onevent(value);
            break;
        case Media.EVENT_POSITION :
            media._position = Number(value.data.position);
            media._mediaEventCallBack.onevent && media._mediaEventCallBack.onevent(value);
            break;
        case Media.EVENT_BUFFERINGPROGRESS :
            media._mediaEventCallBack.onevent && media._mediaEventCallBack.onevent(value);
            break;
        case Media._MEDIA_CONTAINER :
            media._containerElem = value.data.containerElem;
            break;
        case Media._MEDIA_ERROR :
            media._mediaEventCallBack.onerror && media._mediaEventCallBack.onerror(value);
            break;
        default :
            console.log('Unhandled Media.mediaEvent :: ' + value.type);
            break;
        }
    }
    else {
         console.log('Received Media.onStatus callback for unknown media:: ' + id);
    }
};

module.exports = Media;
<|endoftext|>


<commit_before>// Register callbacks to desired call events
var eventHandlers = {

    'progress':   function(e){
                        $("#dial").html(' Calling');
                        $( "#phone-dial" ).toggleClass( "spin", true );
                        $( "#dial" ).toggleClass( "disabled", true );
                        $( "#dial" ).attr('disabled', 'disabled');
                        $( "#hangup" ).toggleClass( "disabled", false );
                        $( "#mute" ).toggleClass( "disabled", false );
                    },
    'failed':     function(e){
                        $( "#dial" ).html('Failed to Connect');
                        $( "#dial" ).toggleClass( "incall", true );
                        $( "#dial" ).attr('disabled', 'disabled');
                        $( "#hangup" ).toggleClass( "disabled", true );
                        $( "#mute" ).toggleClass( "disabled", true );
                        timeout = setTimeout(function() {
                            $("#dial").html('');
                            $( "#dial" ).removeAttr('disabled');
                            $( "#dial" ).toggleClass( "incall", false );
                            $( "#dial" ).toggleClass( "disabled", false );
                        }, 2500);
                    },
    'started':    function(e){
                        // Start stopwatch
                        stopwatch.startStop();
                        $( "#dial" ).toggleClass( "incall", true );
                        $( "#dial" ).attr('disabled', 'disabled');
                    },
    'ended':      function(e){
                        // Stop stopwatch
                        stopwatch.startStop();

                        $("#dial").html('Call Ended');
                        $( "#hangup" ).toggleClass( "disabled", true );
                        $( "#mute" ).toggleClass( "disabled", true );
                        timeout = setTimeout(function() {
                                $( "#dial" ).toggleClass( "incall", false );
                                $( "#dial" ).toggleClass( "disabled", false );
                                $( "#dial" ).removeAttr('disabled');
                            }, 2000);
                        timeout = setTimeout(function() {
                                $("#dial").html('');
                            }, 2500);
                    }
};

/** Make call Event **/
var callTimer;
function makeCall(){
        window.clearTimeout(callTimer);
        $( "#dial" ).html(' Dialing');
        voxbone.WebRTC.call(document.getElementById("number").value);
        $( "#phone-dial" ).toggleClass( "spin", true );
        $( "#dial" ).toggleClass( "disabled", true );
        $( "#dial" ).attr('disabled', 'disabled');
        $( "#hangup" ).toggleClass( "disabled", true );
        $( "#mute" ).toggleClass( "disabled", true );
};

// Terminate call when page refreshed/exited
// window.onbeforeunload=function(e){
//     rtcSession.terminate();
// }

/** This part is required as it handle Voxbone WebRTC initialization **/
function init(){
    // Set the webrtc auth server url (url below it the default one)
    voxbone.WebRTC.authServerURL = "https://webrtc.voxbone.com/rest/authentication/createToken";

    // Force the preferedPop to TEST.
    //This can be usefull if you need  to get your webrtc calls troubleshooted
    //If this is not set, a ping to each pop will be issued to determine which is the most optimal for the user
    //Default is to use the ping mechanism to determine the preferedPop.
    //voxbone.WebRTC.preferedPop = 'TEST';

    // set custom event handlers
    voxbone.WebRTC.customEventHandler = eventHandlers;

    //Set the caller-id, domain name gets automatically stripped off
    //Note that It must be a valid sip uri.
    //Default value is: voxrtc@voxbone.com
    //voxbone.WebRTC.configuration.uri = "caller-id@voxbone.com";

    //Bootstrap Voxbone WebRTC javascript object
    voxbone.WebRTC.init(voxrtc_config);

    /** Parse through Email footer url for Number value to call **/
    function getURLParameter(name) {
            return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search)||[,""])[1].replace(/\+/g, '%20'))||null;
        }
    var number = getURLParameter('number');
    document.getElementById('number').value = '+'+number;

    /** Additional Parameters for profile name **/
    // var firstname = getURLParameter('firstname');
    // var lastname = getURLParameter('lastname');
    // document.getElementById('firstname').value = firstname + ' ' + lastname;

    /** Onload Wait for Auth then Make call **/
    callTimer = window.setInterval(function(){makeCall()},1000);

    /** Onload disable buttons **/
    $( "#dial" ).toggleClass( "disabled", true );
    $( "#dial" ).attr('disabled', 'disabled');
    $( "#hangup" ).toggleClass( "disabled", true );
    $( "#mute" ).toggleClass( "disabled", true );
};

/** Optional part, only use to play with mute **/
function toggleMute(){
    var button = document.getElementById("mute");
    if( voxbone.WebRTC.isMuted ){
        voxbone.WebRTC.unmute();
        button.value = "mute";
        $( "#mute" ).toggleClass( "muted", false );

    }else{
        voxbone.WebRTC.mute();
        button.value = "unmute";
        $( "#mute" ).toggleClass( "muted", true );

    }
}
/** Call timer **/
var stopwatch = new Stopwatch(function(runtime) {
   // format time as m:ss
   var minutes = Math.floor(runtime / 60000);
   var seconds = Math.floor(runtime % 60000 / 1000);
   var displayText = minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
   $("#dial").html("In Call " + displayText);
});<commit_msg>Update call.js<commit_after>//Add your webRTC credentials
var username ="";
var secret = "";
/** Make call Event **/
var callTimer;
function makeCall(){
        window.clearTimeout(callTimer);
        $( "#dial" ).html(' Dialing');
        voxbone.WebRTC.call(document.getElementById("number").value);
        $( "#phone-dial" ).toggleClass( "spin", true );
        $( "#dial" ).toggleClass( "disabled", true );
        $( "#dial" ).attr('disabled', 'disabled');
        $( "#hangup" ).toggleClass( "disabled", true );
        $( "#mute" ).toggleClass( "disabled", true );
};

// Terminate call when page refreshed/exited
// window.onbeforeunload=function(e){
//     rtcSession.terminate();
// }

/** This part is required as it handle Voxbone WebRTC initialization **/
function init(){
    // Set the webrtc auth server url (url below it the default one)
    voxbone.WebRTC.authServerURL = "https://webrtc.voxbone.com/rest/authentication/createToken";

    // Force the preferedPop to TEST.
    //This can be usefull if you need  to get your webrtc calls troubleshooted
    //If this is not set, a ping to each pop will be issued to determine which is the most optimal for the user
    //Default is to use the ping mechanism to determine the preferedPop.
    //voxbone.WebRTC.preferedPop = 'TEST';

    // set custom event handlers
      voxbone.WebRTC.customEventHandler.progress = function(e) {
		$("#dial").html(' Calling');
        $( "#phone-dial" ).toggleClass( "spin", true );
        $( "#dial" ).toggleClass( "disabled", true );
        $( "#dial" ).attr('disabled', 'disabled');
        $( "#hangup" ).toggleClass( "disabled", false );
        $( "#mute" ).toggleClass( "disabled", false );
	  };
	  voxbone.WebRTC.customEventHandler.getUserMediaFailed = function(e) {
		$( "#dial" ).html('Failed to Connect');
        $( "#dial" ).toggleClass( "incall", true );
        $( "#dial" ).attr('disabled', 'disabled');
        $( "#hangup" ).toggleClass( "disabled", true );
        $( "#mute" ).toggleClass( "disabled", true );
        timeout = setTimeout(function() {
            $("#dial").html('');
            $( "#dial" ).removeAttr('disabled');
            $( "#dial" ).toggleClass( "incall", false );
            $( "#dial" ).toggleClass( "disabled", false );
        }, 2500);
	  };
	  };
  	  voxbone.WebRTC.customEventHandler.failed = function(e) {
		$( "#dial" ).html('Failed to Connect');
        $( "#dial" ).toggleClass( "incall", true );
        $( "#dial" ).attr('disabled', 'disabled');
        $( "#hangup" ).toggleClass( "disabled", true );
        $( "#mute" ).toggleClass( "disabled", true );
        timeout = setTimeout(function() {
            $("#dial").html('');
            $( "#dial" ).removeAttr('disabled');
            $( "#dial" ).toggleClass( "incall", false );
            $( "#dial" ).toggleClass( "disabled", false );
        }, 2500);
	  };
      voxbone.WebRTC.customEventHandler.accepted = function(e) {
		 // Start stopwatch
        stopwatch.startStop();
        $( "#dial" ).toggleClass( "incall", true );
        $( "#dial" ).attr('disabled', 'disabled');
	  };
      voxbone.WebRTC.customEventHandler.ended = function(e) {
		 // Stop stopwatch
        stopwatch.startStop();
        $("#dial").html('Call Ended');
        $( "#hangup" ).toggleClass( "disabled", true );
        $( "#mute" ).toggleClass( "disabled", true );
        timeout = setTimeout(function() {
                $( "#dial" ).toggleClass( "incall", false );
                $( "#dial" ).toggleClass( "disabled", false );
                $( "#dial" ).removeAttr('disabled');
            }, 2000);
        timeout = setTimeout(function() {
                $("#dial").html('');
            }, 2500);
        }
	  };

    //Set the caller-id, domain name gets automatically stripped off
    //Note that It must be a valid sip uri.
    //Default value is: voxrtc@voxbone.com
    //voxbone.WebRTC.configuration.uri = "caller-id@voxbone.com";

    //Bootstrap Voxbone WebRTC javascript object
    //voxbone.WebRTC.init(voxrtc_config);
    voxbone.WebRTC.basicAuthInit(username, secret);

    /** Parse through Email footer url for Number value to call **/
    function getURLParameter(name) {
            return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search)||[,""])[1].replace(/\+/g, '%20'))||null;
        }
    var number = getURLParameter('number');
    document.getElementById('number').value = '+'+number;

    /** Additional Parameters for profile name **/
    // var firstname = getURLParameter('firstname');
    // var lastname = getURLParameter('lastname');
    // document.getElementById('firstname').value = firstname + ' ' + lastname;

    /** Onload Wait for Auth then Make call **/
    callTimer = window.setInterval(function(){makeCall()},1000);

    /** Onload disable buttons **/
    $( "#dial" ).toggleClass( "disabled", true );
    $( "#dial" ).attr('disabled', 'disabled');
    $( "#hangup" ).toggleClass( "disabled", true );
    $( "#mute" ).toggleClass( "disabled", true );
};

/** Optional part, only use to play with mute **/
function toggleMute(){
    var button = document.getElementById("mute");
    if( voxbone.WebRTC.isMuted ){
        voxbone.WebRTC.unmute();
        button.value = "mute";
        $( "#mute" ).toggleClass( "muted", false );

    }else{
        voxbone.WebRTC.mute();
        button.value = "unmute";
        $( "#mute" ).toggleClass( "muted", true );

    }
}
/** Call timer **/
var stopwatch = new Stopwatch(function(runtime) {
   // format time as m:ss
   var minutes = Math.floor(runtime / 60000);
   var seconds = Math.floor(runtime % 60000 / 1000);
   var displayText = minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
   $("#dial").html("In Call " + displayText);
});
<|endoftext|>


<commit_before>const Kolibri = require('kolibri');

function addClassroom({ dispatch }, attrs) {
  dispatch('ADD_CLASSROOM', attrs);
}

function setSelectedClassroomId({ dispatch }, id) {
  dispatch('SET_SELECTED_CLASSROOM_ID', id);
}

function setSelectedGroupId({ dispatch }, id) {
  dispatch('SET_SELECTED_GROUP_ID', id);
}

const ClassroomResource = Kolibri.resources.ClassroomResource;
const LearnerGroupResource = Kolibri.resources.LearnerGroupResource;
const MembershipResource = Kolibri.resources.MembershipResource;
const FacilityUserResource = Kolibri.resources.FacilityUserResource;
const RoleResource = Kolibri.resources.RoleResource;

function createUser(store, payload, role) {
  const FacilityUserModel = FacilityUserResource.addModelData(payload);
  const newUserPromise = FacilityUserModel.save(payload);
  newUserPromise.then((model) => {
    // always add role atrribute to facilityUser
    model.attributes.role = role;
    // assgin role to this new user if the role is not learner
    if (role === 'learner') {
      // mutation ADD_LEARNERS only take array
      store.dispatch('ADD_LEARNERS', [model]);
    } else {
      const rolePayload = {
        user: model.attributes.id,
        collection: model.attributes.facility,
        kind: role,
      };
      const RoleModel = RoleResource.addModelData(rolePayload);
      const newRolePromise = RoleModel.save(rolePayload);
      newRolePromise.then((results) => {
        // mutation ADD_LEARNERS only take array
        store.dispatch('ADD_LEARNERS', [model]);
      }).catch((error) => {
        store.dispatch('SET_ERROR', JSON.stringify(error, null, '\t'));
      });
    }
  })
  .catch((error) => {
    store.dispatch('SET_ERROR', JSON.stringify(error, null, '\t'));
  });
}

function deleteUser(store, id) {
  const FacilityUserModel = Kolibri.resources.FacilityUserResource.getModel(id);
  const newUserPromise = FacilityUserModel.delete(id);
  newUserPromise.then((userId) => {
    store.dispatch('DELETE_LEARNERS', [userId]);
  })
  .catch((error) => {
    store.dispatch('SET_ERROR', JSON.stringify(error, null, '\t'));
  });
}

// An action for setting up the initial state of the app by fetching data from the server
function fetch({ dispatch }) {
  const classroomCollection = ClassroomResource.getCollection();
  const learnerGroupCollection = LearnerGroupResource.getCollection();
  const learnerCollection = FacilityUserResource.getCollection();
  const memberCollection = MembershipResource.getCollection();

  const learnerGroupPromise = new Promise((resolve) => {
    classroomCollection.fetch().then(() => {
      const cid = classroomCollection.models.map(c => c.id)[0];
      learnerGroupCollection.fetch({ params: { parent: cid } }).then((groupData) => {
        resolve(groupData);
      });
    });
  });

  const learnerPromise = learnerCollection.fetch();

  const memberPromise = memberCollection.fetch();

  const promises = [learnerGroupPromise, learnerPromise, memberPromise];
  // This block of code is executed if learnerGroup, learner, and membership
  // fetching all return with status code 200.
  // class fetching is not included because learnerGroup fetching depends upon it.
  Promise.all(promises).then(() => {
    const groupedLearners = new Set();
    dispatch('ADD_LEARNER_GROUPS', learnerGroupCollection.models.map(group => {
      const learnerIds = memberCollection.models.filter(m => m.collection === group.id)
        .map(m => m.user);
      learnerIds.forEach(id => groupedLearners.add(id));
      return Object.assign({}, group, {
        learners: learnerCollection.models.filter(learner => learnerIds.indexOf(learner.id) !== -1)
          .map(learner => learner.id),
      });
    }));

    dispatch(
      'ADD_CLASSROOMS',
      classroomCollection.models.map(classroom => {
        const learnerIds = memberCollection.models.filter(m => m.collection === classroom.id)
          .map(m => m.user);
        const ungroupedLearners = learnerIds.filter(id => !groupedLearners.has(id));
        return Object.assign(
          {},
          classroom,
          {
            learnerGroups: learnerGroupCollection.models.filter(g => g.parent === classroom.id)
              .map(g => g.id),
            ungroupedLearners,
          }
        );
      })
    );
    dispatch('ADD_LEARNERS', learnerCollection.models);
  });
}

module.exports = {
  addClassroom,
  setSelectedClassroomId,
  setSelectedGroupId,
  fetch,
  createUser,
  deleteUser,
};
<commit_msg>delete unused code for mvp, including classroom, learnergroup..<commit_after>const Kolibri = require('kolibri');

const FacilityUserResource = Kolibri.resources.FacilityUserResource;
const RoleResource = Kolibri.resources.RoleResource;

function createUser(store, payload, role) {
  const FacilityUserModel = FacilityUserResource.addModelData(payload);
  const newUserPromise = FacilityUserModel.save(payload);
  newUserPromise.then((model) => {
    // always add role atrribute to facilityUser
    model.attributes.role = role;
    // assgin role to this new user if the role is not learner
    if (role === 'learner') {
      // mutation ADD_LEARNERS only take array
      store.dispatch('ADD_LEARNERS', [model]);
    } else {
      const rolePayload = {
        user: model.attributes.id,
        collection: model.attributes.facility,
        kind: role,
      };
      const RoleModel = RoleResource.addModelData(rolePayload);
      const newRolePromise = RoleModel.save(rolePayload);
      newRolePromise.then((results) => {
        // mutation ADD_LEARNERS only take array
        store.dispatch('ADD_LEARNERS', [model]);
      }).catch((error) => {
        store.dispatch('SET_ERROR', JSON.stringify(error, null, '\t'));
      });
    }
  })
  .catch((error) => {
    store.dispatch('SET_ERROR', JSON.stringify(error, null, '\t'));
  });
}

function deleteUser(store, id) {
  const FacilityUserModel = Kolibri.resources.FacilityUserResource.getModel(id);
  const newUserPromise = FacilityUserModel.delete(id);
  newUserPromise.then((userId) => {
    store.dispatch('DELETE_LEARNERS', [userId]);
  })
  .catch((error) => {
    store.dispatch('SET_ERROR', JSON.stringify(error, null, '\t'));
  });
}

// An action for setting up the initial state of the app by fetching data from the server
function fetch({ dispatch }) {
  const learnerCollection = FacilityUserResource.getCollection();
  const learnerPromise = learnerCollection.fetch();
  const promises = [learnerPromise];
  Promise.all(promises).then(() => {
    dispatch('ADD_LEARNERS', learnerCollection.models);
  });
}

module.exports = {
  createUser,
  updateUser,
  deleteUser,
  fetch,
};
<|endoftext|>


<commit_before>OC.L10N.register(
    "audioplayer",
    {
	"Welcome to" : "Willkommen bei",
	"Audio Player" : "Audio-Player",
	"Add new tracks to library" : "Der Bibliothek neue Titel hinzufügen",
	"Scan for new audio files" : "Nach neuen Audiodateien suchen",
	"New or updated audio files available" : "Neue oder aktualisierte Audiodateien verfügbar",
	"Help" : "Hilfe",
	"Sort playlist" : "Wiedergabeliste sortieren",
	"Rename playlist" : "Wiedergabeliste umbenennen",
	"Delete playlist" : "Wiedergabeliste löschen",
	"Add new tracks to playlist by drag and drop" : "Der Wiedergabeliste neue Titel per Drag und Drop hinzufügen",
	"No playlist selected!" : "Keine Wiedergabeliste ausgewählt!",
	"Sort modus active" : "Sortiermodus aktiv",
	"Playlist successfully deleted!" : "Wiedergabeliste erfolgreich gelöscht!",
	"Start scanning…" : "Scannen starten…",
	"Scanning finished! New Audios found!" : "Scannen beendet! Neue Audiodateien gefunden!",
	"Yes" : "Ja",
	"No" : "Nein",
	"Error" : "Fehler",
	"Are you sure?" : "Sind Sie sicher?",
	"Disc" : "Disc",
	"Track" : "Stück",
	"File" : "Datei",
	"Title" : "Titel",
	"Subtitle" : "Untertitel",
	"Artist" : "Interpret",
	"Album Artist" : "Albuminterpret",
	"Genre" : "Genre",
	"Year" : "Jahr",
	"Album" : "Album",
	"Albums" : "Alben",
	"Composer" : "Komponist",
	"Edit metadata" : "Metadaten bearbeiten",
	"No ID3 editor installed" : "Kein ID3-Editor installiert",
	"No playlist entry" : "Kein Wiedergabelisten-Eintrag",
	"Remove" : "Entfernen",
	"Options" : "Optionen",
	"MIME type" : "MIME-Typ",
	"MIME type not supported by browser" : "MIME-Typ vom Browser nicht unterstützt",
	"Missing permission to edit metadata of track!" : "Fehlende Berechtigung zum Bearbeiten der Metadaten des Stücks!",
	"Existing Albums" : "Vorhandene Alben",
	"New Album" : "Neues Album",
	"Existing Artists" : "Vorhandene Interpreten",
	"New Artist" : " Neuer Interpret",
	"Existing Genres" : "Vorhandene Genres",
	"New Genre" : "Neues Genre",
	"Add as Album Cover" : "Als Album-Cover verwenden",
	"of" : "von",
	"Total" : "Gesamt",
	"Select from cloud" : "Bild aus Cloud wählen",
	"Edit" : "Bearbeiten",
	"Delete" : "Löschen",
	"Upload" : "Hochladen",
	"Drop picture" : "Bild fallen lassen",
	"Select picture" : "Bild auswählen",
	"Picture generating, please wait…" : "Bilderzeugung, bitte warten…",
	"Drag picture here!" : "Bild hierher ziehen",
	"Settings" : "Einstellungen",
	"Select a single folder with audio files" : "Einzelnen Ordner mit Audiodateien auswählen",
	"Invalid path!" : "Ungültiger Pfad!",
	"saved" : "gespeichert",
	"Reset library" : "Bibliothek zurücksetzen",
	"Start resetting library…" : "Bibliothek wird zurückgesetzt…",
	"Resetting finished!" : "Zurücksetzen beendet!",
	"All library entries will be deleted!" : "Alle Bibliothekseinträge werden gelöscht!",
	"Close" : "Schließen",
	"Play" : "Abspielen",
	"Save" : "Speichern",
	"Selected" : "Ausgewählt",
	"Selected Album" : "Ausgewähltes Album",
	"Selected Artist" : "Ausgewählter Interpret",
	"Selected Album Artist" : "Ausgewählter Albuminterpret",
	"Selected Composer" : "Ausgewählter Komponist",
	"Selected Folder" : "Ausgewählter Ordner",
	"Selected Genre" : "Ausgewähltes Genre",
	"Selected Playlist" : "Ausgewählte Wiedergabeliste",
	"Selected Year" : "Ausgewähltes Jahr",
	"Length" : "Länge"
},
"");
<commit_msg>Editor, Playlist, and Lyrics added<commit_after>OC.L10N.register(
    "audioplayer",
    {
	"Welcome to" : "Willkommen bei",
	"Audio Player" : "Audio-Player",
	"Add new tracks to library" : "Der Bibliothek neue Titel hinzufügen",
	"Scan for new audio files" : "Nach neuen Audiodateien suchen",
	"New or updated audio files available" : "Neue oder aktualisierte Audiodateien verfügbar",
	"Help" : "Hilfe",
	"Sort playlist" : "Wiedergabeliste sortieren",
	"Rename playlist" : "Wiedergabeliste umbenennen",
	"Delete playlist" : "Wiedergabeliste löschen",
	"Add new tracks to playlist by drag and drop" : "Der Wiedergabeliste neue Titel per Drag und Drop hinzufügen",
	"No playlist selected!" : "Keine Wiedergabeliste ausgewählt!",
	"Sort modus active" : "Sortiermodus aktiv",
	"Playlist successfully deleted!" : "Wiedergabeliste erfolgreich gelöscht!",
	"Start scanning…" : "Scannen starten…",
	"Scanning finished! New Audios found!" : "Scannen beendet! Neue Audiodateien gefunden!",
	"Yes" : "Ja",
	"No" : "Nein",
	"Error" : "Fehler",
	"Are you sure?" : "Sind Sie sicher?",
	"Disc" : "Disc",
	"Track" : "Stück",
	"File" : "Datei",
	"Title" : "Titel",
	"Subtitle" : "Untertitel",
	"Artist" : "Interpret",
	"Album Artist" : "Albuminterpret",
	"Genre" : "Genre",
	"Year" : "Jahr",
	"Album" : "Album",
	"Albums" : "Alben",
	"Composer" : "Komponist",
	"Metadata" : "Metadaten",
	"Edit metadata" : "Metadaten bearbeiten",
	"ID3 Editor" : "ID3-Editor",
	"No ID3 editor installed" : "Kein ID3-Editor installiert",
	"Playlists" : "Wiedergabelisten",
	"No playlist entry" : "Kein Wiedergabelisten-Eintrag",
	"Lyrics" : "Liedtexte",
	"No lyrics found" : "Kein Liedtext gefunden",
	"Remove" : "Entfernen",
	"Options" : "Optionen",
	"MIME type" : "MIME-Typ",
	"MIME type not supported by browser" : "MIME-Typ vom Browser nicht unterstützt",
	"Missing permission to edit metadata of track!" : "Fehlende Berechtigung zum Bearbeiten der Metadaten des Stücks!",
	"Existing Albums" : "Vorhandene Alben",
	"New Album" : "Neues Album",
	"Existing Artists" : "Vorhandene Interpreten",
	"New Artist" : " Neuer Interpret",
	"Existing Genres" : "Vorhandene Genres",
	"New Genre" : "Neues Genre",
	"Add as Album Cover" : "Als Album-Cover verwenden",
	"of" : "von",
	"Total" : "Gesamt",
	"Select from cloud" : "Bild aus Cloud wählen",
	"Edit" : "Bearbeiten",
	"Delete" : "Löschen",
	"Upload" : "Hochladen",
	"Drop picture" : "Bild fallen lassen",
	"Select picture" : "Bild auswählen",
	"Picture generating, please wait…" : "Bilderzeugung, bitte warten…",
	"Drag picture here!" : "Bild hierher ziehen",
	"Settings" : "Einstellungen",
	"Select a single folder with audio files" : "Einzelnen Ordner mit Audiodateien auswählen",
	"Invalid path!" : "Ungültiger Pfad!",
	"saved" : "gespeichert",
	"Reset library" : "Bibliothek zurücksetzen",
	"Start resetting library…" : "Bibliothek wird zurückgesetzt…",
	"Resetting finished!" : "Zurücksetzen beendet!",
	"All library entries will be deleted!" : "Alle Bibliothekseinträge werden gelöscht!",
	"Close" : "Schließen",
	"Play" : "Abspielen",
	"Save" : "Speichern",
	"Selected" : "Ausgewählt",
	"Selected Album" : "Ausgewähltes Album",
	"Selected Artist" : "Ausgewählter Interpret",
	"Selected Album Artist" : "Ausgewählter Albuminterpret",
	"Selected Composer" : "Ausgewählter Komponist",
	"Selected Folder" : "Ausgewählter Ordner",
	"Selected Genre" : "Ausgewähltes Genre",
	"Selected Playlist" : "Ausgewählte Wiedergabeliste",
	"Selected Year" : "Ausgewähltes Jahr",
	"Length" : "Länge"
},
"");
<|endoftext|>


<commit_before>/* get list of plots to work on... */

$(document).ready(function() {
    load_all_plots();

    var categories = JSON.parse($('#cea-dashboard-add-plot').attr('data-cea-categories'));
    console.log("assigned categories!");
    console.log(categories);

    $("[id|=cea-plot-category]").on("change", function(e){
        let category_name = this.value;
        $("[id|=cea-plot-name]").empty();
        $.each(categories[category_name]["plots"], function(i, p){
            console.log(p);
            $("[id|=cea-plot-name]").append($("<option></option>").attr("value", p.id).text(p.name));
        });

        console.log(categories[category_name]["plots"]);
    });

    $("#cea-dashboard-edit-plot").on("show.bs.modal", function (e) {
        let plot_index = e.relatedTarget.dataset.plotIndex;
        let dashboard_index = e.relatedTarget.dataset.dashboardIndex;
        let url = "plot-parameters/" + dashboard_index + "/" + plot_index;
        $.get(url, function (data) {
            $("#cea-dashboard-edit-plot-form").html(data)
                .attr("action", url)
                .attr("method", "POST");
            $(".selectpicker").selectpicker({"actionsBox": true});
            $(".js-switch").each(function(_, checkbox){
                console.log("setting up Switchery");
                console.log(checkbox);
                switcher = new Switchery(checkbox);
            });
        }).fail(function (data) {
            console.log("something went terribly wrong?!");
            console.log(data);
        });
    });

    $("#cea-dashboard-replace-plot").on("show.bs.modal", function (e) {
        let plot_index = e.relatedTarget.dataset.plotIndex;
        let dashboard_index = e.relatedTarget.dataset.dashboardIndex;
        console.log(plot_index, dashboard_index);
        let url = "replace-plot/" + dashboard_index + "/" + plot_index;
        $("#cea-dashboard-replace-plot-form").attr("action", url)
            .attr("method", "POST");
    });

    $("#cea-dashboard-edit-plot-form").submit(function (e) {
        e.preventDefault();
        $.post($("#cea-dashboard-edit-plot-form").attr("action"), get_parameter_values(), function(data){
            console.log(data);
            location.reload();
        }, "json");
        return false;
    });

    $("#dashboard-selector").val(window.location.href.split('/').pop().replace(/[^0-9]/g, ''))
        .change(function () {
            if($(this).val() === "manage") {
                window.location.href = "./" + "manage";
            } else if($(this).val() !== "new") {
                window.location.href = "./" + $(this).val();
            }
        });
});

function load_all_plots() {
    $(".cea-plot").map(function() {
        let dashboard_index = this.dataset.ceaDashboardIndex;
        let plot_index = this.dataset.ceaPlotIndex;
        let x_content_id = "#x_content-" + dashboard_index + "-" + plot_index;
        let x_table_id = "#x_table-" + dashboard_index + "-" + plot_index;

        $.get("../div/" + dashboard_index + "/" + plot_index, function(data){
            $(x_content_id).children().replaceWith(data);
            $('#table-btn-'+plot_index).show();
        }).fail(function(data) {
            // Server error
            if (data.status === 500) {
                $(x_content_id).children().replaceWith("ERROR: " + $(data.responseText).filter("p").text());
                console.log("error creating plot: "+x_content_id);
                console.log(data);
            }
            // Missing files
            if (data.status === 404) {
                $(x_content_id).children().replaceWith(data.responseText);
            }
        });

        // $.get("../table/" + dashboard_index + "/" + plot_index, function(data){
        //         $(x_table_id).children().replaceWith(data);
        // }).fail(function(data) {
        //     $(x_table_id).children().replaceWith("");
        //     console.log("error creating plot:");
        //     console.log(data);
        // });
    });
}

function openSettings(dashboard_index, func) {
    $.get('./' + func + '/' + dashboard_index, {}, function(html) {
        $('#cea-prompt .modal-content').html(html);
        $('#cea-prompt').modal({'show': true, 'backdrop': 'static'});
    });
}

function add_new_dashboard() {
    $.get("new", function (html) {
        $("#cea-prompt .modal-content").html(html);
        $("#cea-prompt").modal({"show": true, "backdrop": "static"});
    });
}

function open_table(element) {
    let style = document.styleSheets[0].ownerNode.cloneNode();
    let height = 500;
    let width = 900;
    let table = window.open("../table/" + element.dataset.dashboardIndex + "/" + element.dataset.plotIndex,
        'popUpWindow', 'height='+height+',width='+width+',location=no,menubar=no,status=no,titlebar=no,resizable');
    table.onload = function() {
        table.document.title = 'City Energy Analyst | ' + element.dataset.plotTitle + ' Table';
        table.document.body.innerHTML = table.document.body.innerHTML || '
Table does not exist.
';
        table.document.body.innerHTML = '
'+element.dataset.plotTitle+'

' + table.document.body.innerHTML;
        table.document.head.append(style);
    }
}

window.addEventListener('resize', function () {
    console.log('resizing');
    $.each($('.plotly-graph-div.js-plotly-plot'), function (index) {
        Plotly.Plots.resize($(this).attr('id'));
    });
});<commit_msg>Replace string concatenation with template<commit_after>/* get list of plots to work on... */

$(document).ready(function() {
    load_all_plots();

    var categories = JSON.parse($('#cea-dashboard-add-plot').attr('data-cea-categories'));
    console.log("assigned categories!");
    console.log(categories);

    $("[id|=cea-plot-category]").on("change", function(){
        let category_name = this.value;
        $("[id|=cea-plot-name]").empty();
        $.each(categories[category_name]["plots"], function(i, p){
            console.log(p);
            $("[id|=cea-plot-name]").append($("<option></option>").attr("value", p.id).text(p.name));
        });

        console.log(categories[category_name]["plots"]);
    });

    $("#cea-dashboard-edit-plot").on("show.bs.modal", function (e) {
        let plot_index = e.relatedTarget.dataset.plotIndex;
        let dashboard_index = e.relatedTarget.dataset.dashboardIndex;
        let url = `plot-parameters/${dashboard_index}/${plot_index}`;
        $.get(url, function (data) {
            $("#cea-dashboard-edit-plot-form").html(data)
                .attr("action", url)
                .attr("method", "POST");
            $(".selectpicker").selectpicker({"actionsBox": true});
            $(".js-switch").each(function(_, checkbox){
                console.log("setting up Switchery");
                console.log(checkbox);
                switcher = new Switchery(checkbox);
            });
        }).fail(function (data) {
            console.log("something went terribly wrong?!");
            console.log(data);
        });
    });

    $("#cea-dashboard-replace-plot").on("show.bs.modal", function (e) {
        let plot_index = e.relatedTarget.dataset.plotIndex;
        let dashboard_index = e.relatedTarget.dataset.dashboardIndex;
        let url = `replace-plot/${dashboard_index}/${plot_index}`;
        $("#cea-dashboard-replace-plot-form").attr("action", url)
            .attr("method", "POST");
    });

    $("#cea-dashboard-edit-plot-form").submit(function (e) {
        e.preventDefault();
        $.post($("#cea-dashboard-edit-plot-form").attr("action"), get_parameter_values(), function(data){
            console.log(data);
            location.reload();
        }, "json");
        return false;
    });

    $("#dashboard-selector").val(window.location.href.split('/').pop().replace(/[^0-9]/g, ''))
        .change(function () {
            let value = $(this).val();
            if(value === "manage") {
                window.location.href = "./manage";
            } else if(value !== "new") {
                window.location.href = `./${value}`;
            }
        });
});

function load_all_plots() {
    $(".cea-plot").map(function() {
        let dashboard_index = this.dataset.ceaDashboardIndex;
        let plot_index = this.dataset.ceaPlotIndex;
        let content_div = $(`#x_content-${dashboard_index}-${plot_index}`);

        $.get(`../div/${dashboard_index}/${plot_index}`, function(data){
            content_div.children().replaceWith(data);
            $('#table-btn-'+plot_index).show();
        }).fail(function(data) {
            // Server error
            if (data.status === 500) {
                content_div.children().replaceWith("ERROR: " + $(data.responseText).filter("p").text());
                console.log("error creating plot: ", `#x_content-${dashboard_index}-${plot_index}`);
                console.log(data);
            }
            // Missing files
            if (data.status === 404) {
                content_div.children().replaceWith(data.responseText);
            }
        });
    });
}

function openSettings(dashboard_index, func) {
    $.get('./' + func + '/' + dashboard_index, {}, function(html) {
        $('#cea-prompt .modal-content').html(html);
        $('#cea-prompt').modal({'show': true, 'backdrop': 'static'});
    });
}

function add_new_dashboard() {
    $.get("new", function (html) {
        $("#cea-prompt .modal-content").html(html);
        $("#cea-prompt").modal({"show": true, "backdrop": "static"});
    });
}

function open_table(element) {
    let style = document.styleSheets[0].ownerNode.cloneNode();
    let height = 500;
    let width = 900;
    let table = window.open(`../table/${element.dataset.dashboardIndex}/${element.dataset.plotIndex}`,
        'popUpWindow', `height=${height},width=${width},location=no,menubar=no,status=no,titlebar=no,resizable`);
    table.onload = function() {
        table.document.title = `City Energy Analyst | ${element.dataset.plotTitle} Table`;
        table.document.body.innerHTML = table.document.body.innerHTML || '
Table does not exist.
';
        table.document.body.innerHTML = `<h1>${element.dataset.plotTitle}</h1><br>${table.document.body.innerHTML}`;
        table.document.head.append(style);
    }
}

window.addEventListener('resize', function () {
    console.log('resizing');
    $.each($('.plotly-graph-div.js-plotly-plot'), function () {
        Plotly.Plots.resize($(this).attr('id'));
    });
});<|endoftext|>