<commit_before>/*

Utilities

*/

import marked from 'marked';
import urlObject from 'url';
import moment from 'moment';
import sanitizeHtml from 'sanitize-html';
import getSlug from 'speakingurl';
import { getSetting } from './settings.js';

/**
 * @summary The global namespace for Vulcan utils.
 * @namespace Telescope.utils
 */
export const Utils = {};

/**
 * @summary Convert a camelCase string to dash-separated string
 * @param {String} str
 */
Utils.camelToDash = function (str) {
  return str.replace(/\W+/g, '-').replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase();
};

/**
 * @summary Convert a camelCase string to a space-separated capitalized string
 * See http://stackoverflow.com/questions/4149276/javascript-camelcase-to-regular-form
 * @param {String} str
 */
Utils.camelToSpaces = function (str) {
  return str.replace(/([A-Z])/g, ' $1').replace(/^./, function(str){ return str.toUpperCase(); });
};

/**
 * @summary Convert an underscore-separated string to dash-separated string
 * @param {String} str
 */
Utils.underscoreToDash = function (str) {
  return str.replace('_', '-');
};

/**
 * @summary Convert a dash separated string to camelCase.
 * @param {String} str
 */
Utils.dashToCamel = function (str) {
  return str.replace(/(\-[a-z])/g, function($1){return $1.toUpperCase().replace('-','');});
};

/**
 * @summary Convert a string to camelCase and remove spaces.
 * @param {String} str
 */
Utils.camelCaseify = function(str) {
  str = this.dashToCamel(str.replace(' ', '-'));
  str = str.slice(0,1).toLowerCase() + str.slice(1);
  return str;
};

/**
 * @summary Trim a sentence to a specified amount of words and append an ellipsis.
 * @param {String} s - Sentence to trim.
 * @param {Number} numWords - Number of words to trim sentence to.
 */
Utils.trimWords = function(s, numWords) {

  if (!s)
    return s;

  var expString = s.split(/\s+/,numWords);
  if(expString.length >= numWords)
    return expString.join(" ")+"…";
  return s;
};

/**
 * @summary Trim a block of HTML code to get a clean text excerpt
 * @param {String} html - HTML to trim.
 */
Utils.trimHTML = function (html, numWords) {
  var text = Utils.stripHTML(html);
  return Utils.trimWords(text, numWords);
};

/**
 * @summary Capitalize a string.
 * @param {String} str
 */
Utils.capitalize = function(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
};

Utils.t = function(message) {
  var d = new Date();
  console.log("### "+message+" rendered at "+d.getHours()+":"+d.getMinutes()+":"+d.getSeconds()); // eslint-disable-line
};

Utils.nl2br = function(str) {
  var breakTag = '
';
  return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ breakTag +'$2');
};

Utils.scrollPageTo = function(selector) {
  $('body').scrollTop($(selector).offset().top);
};

Utils.getDateRange = function(pageNumber) {
  var now = moment(new Date());
  var dayToDisplay=now.subtract(pageNumber-1, 'days');
  var range={};
  range.start = dayToDisplay.startOf('day').valueOf();
  range.end = dayToDisplay.endOf('day').valueOf();
  // console.log("after: ", dayToDisplay.startOf('day').format("dddd, MMMM Do YYYY, h:mm:ss a"));
  // console.log("before: ", dayToDisplay.endOf('day').format("dddd, MMMM Do YYYY, h:mm:ss a"));
  return range;
};

//////////////////////////
// URL Helper Functions //
//////////////////////////

/**
 * @summary Returns the user defined site URL or Meteor.absoluteUrl
 */
Utils.getSiteUrl = function () {
  return getSetting('siteUrl', Meteor.absoluteUrl());
};

/**
 * @summary The global namespace for Vulcan utils.
 * @param {String} url - the URL to redirect
 */
Utils.getOutgoingUrl = function (url) {
  return Utils.getSiteUrl() + "out?url=" + encodeURIComponent(url);
};

Utils.slugify = function (s) {
  var slug = getSlug(s, {
    truncate: 60
  });

  // can't have posts with an "edit" slug
  if (slug === "edit") {
    slug = "edit-1";
  }

  return slug;
};

Utils.getUnusedSlug = function (collection, slug) {
  let suffix = "";
  let index = 0;

  // test if slug is already in use
  while (!!collection.findOne({slug: slug+suffix})) {
    index++;
    suffix = "-"+index;
  }

  return slug+suffix;
};

Utils.getShortUrl = function(post) {
  return post.shortUrl || post.url;
};

Utils.getDomain = function(url) {
  try {
    return urlObject.parse(url).hostname.replace('www.', '');
  } catch (error) {
    return null;
  }
};

Utils.invitesEnabled = function() {
  return getSetting("requireViewInvite") || getSetting("requirePostInvite");
};

// add http: if missing
Utils.addHttp = function (url) {
  try {
    if (url.substring(0, 5) !== "http:" && url.substring(0, 6) !== "https:") {
      url = "http:"+url;
    }
    return url;
  } catch (error) {
    return null;
  }
};

/////////////////////////////
// String Helper Functions //
/////////////////////////////

Utils.cleanUp = function(s) {
  return this.stripHTML(s);
};

Utils.sanitize = function(s) {
  // console.log('// before sanitization:')
  // console.log(s)
  if(Meteor.isServer){
    s = sanitizeHtml(s, {
      allowedTags: [
        'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul',
        'ol', 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike',
        'code', 'hr', 'br', 'div', 'table', 'thead', 'caption',
        'tbody', 'tr', 'th', 'td', 'pre', 'img'
      ]
    });
    // console.log('// after sanitization:')
    // console.log(s)
  }
  return s;
};

Utils.stripHTML = function(s) {
  return s.replace(/<(?:.|\n)*?>/gm, '');
};

Utils.stripMarkdown = function(s) {
  var htmlBody = marked(s);
  return Utils.stripHTML(htmlBody);
};

// http://stackoverflow.com/questions/2631001/javascript-test-for-existence-of-nested-object-key
Utils.checkNested = function(obj /*, level1, level2, ... levelN*/) {
  var args = Array.prototype.slice.call(arguments);
  obj = args.shift();

  for (var i = 0; i < args.length; i++) {
    if (!obj.hasOwnProperty(args[i])) {
      return false;
    }
    obj = obj[args[i]];
  }
  return true;
};

Utils.log = function (s) {
  if(getSetting('debug', false) || process.env.NODE_ENV === "development") {
    console.log(s); // eslint-disable-line
  }
};

// see http://stackoverflow.com/questions/8051975/access-object-child-properties-using-a-dot-notation-string
Utils.getNestedProperty = function (obj, desc) {
  var arr = desc.split(".");
  while(arr.length && (obj = obj[arr.shift()]));
  return obj;
};

// see http://stackoverflow.com/a/14058408/649299
_.mixin({
  compactObject : function(object) {
    var clone = _.clone(object);
    _.each(clone, function(value, key) {
      if(!value && typeof value !== "boolean") {
        delete clone[key];
      }
    });
    return clone;
  }
});

Utils.getFieldLabel = (fieldName, collection) => {
  const label = collection.simpleSchema()._schema[fieldName].label;
  const nameWithSpaces = Utils.camelToSpaces(fieldName);
  return label || nameWithSpaces;
}

Utils.getLogoUrl = () => {
  const logoUrl = getSetting("logoUrl");
  if (!!logoUrl) {
    const prefix = Utils.getSiteUrl().slice(0,-1);
    // the logo may be hosted on another website
    return logoUrl.indexOf('://') > -1 ? logoUrl : prefix + logoUrl;
  }
};

// note(apollo): get collection's name from __typename given by react-apollo
Utils.getCollectionNameFromTypename = (type) => {
  if (type.indexOf('Post') > -1) {
    return 'posts';
  } else if (type.indexOf('Cat') > -1) {
    return 'categories';
  } else if (type.indexOf('User') > -1) {
    return 'users';
  } else if (type.indexOf('Comment') > -1) {
    return 'comments';
  }
};

Utils.findIndex = (array, predicate) => {
  let index = -1;
  let continueLoop = true;
  array.forEach((item, currentIndex) => {
    if (continueLoop && predicate(item)) {
      index = currentIndex
      continueLoop = false
    }
  });
  return index;
}

// adapted from http://stackoverflow.com/a/22072374/649299
Utils.unflatten = function(array, options, parent, level=0, tree){

  const {
    idProperty = '_id',
    parentIdProperty = 'parentId',
    childrenProperty = 'childrenResults'
  } = options;

  level++;

  tree = typeof tree !== "undefined" ? tree : [];

  let children = [];

  if (typeof parent === "undefined") {
    // if there is no parent, we're at the root level
    // so we return all root nodes (i.e. nodes with no parent)
    children = _.filter(array, node => !node[parentIdProperty]);
  } else {
    // if there *is* a parent, we return all its child nodes
    // (i.e. nodes whose parentId is equal to the parent's id.)
    children = _.filter(array, node => node[parentIdProperty] === parent[idProperty]);
  }

  // if we found children, we keep on iterating
  if (!!children.length) {

    if (typeof parent === "undefined") {
      // if we're at the root, then the tree consist of all root nodes
      tree = children;
    } else {
      // else, we add the children to the parent as the "childrenResults" property
      parent[childrenProperty] = children;
    }

    // we call the function on each child
    children.forEach(child => {
      child.level = level;
      Utils.unflatten(array, options, child, level);
    });
  }

  return tree;
};

// remove the telescope object from a schema and duplicate it at the root
Utils.stripTelescopeNamespace = (schema) => {
  // grab the users schema keys
  const schemaKeys = Object.keys(schema);

  // remove any field beginning by telescope: .telescope, .telescope.upvotedPosts.$, ...
  const filteredSchemaKeys = schemaKeys.filter(key => key.slice(0,9) !== 'telescope');

  // replace the previous schema by an object based on this filteredSchemaKeys
  return filteredSchemaKeys.reduce((sch, key) => ({...sch, [key]: schema[key]}), {});
}

/**
 * Convert an array of field names into a Mongo fields specifier
 * @param {Array} fieldsArray
 */
Utils.arrayToFields = (fieldsArray) => {
  return _.object(fieldsArray, _.map(fieldsArray, function () {return true}));
}

/**
 * Get the display name of a React component
 * @param {React Component} WrappedComponent
 */
Utils.getComponentDisplayName = (WrappedComponent) => {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
};

/**
 * Take a collection and a list of documents, and convert all their date fields to date objects
 * This is necessary because Apollo doesn't support custom scalars, and stores dates as strings
 * @param {Object} collection
 * @param {Array} list
 */
Utils.convertDates = (collection, listOrDocument) => {
  // if undefined, just return
  if (!listOrDocument || !listOrDocument.length) return listOrDocument;

  const list = Array.isArray(listOrDocument) ? listOrDocument : [listOrDocument];
  const schema = collection.simpleSchema()._schema;
  const dateFields = _.filter(_.keys(schema), fieldName => schema[fieldName].type === Date);
  const convertedList = list.map(result => {
    dateFields.forEach(fieldName => {
      if (result[fieldName] && typeof result[fieldName] === 'string') {
        result[fieldName] = new Date(result[fieldName]);
      }
    });
    return result;
  });

  return Array.isArray(listOrDocument) ? convertedList : convertedList[0];
}

Utils.encodeIntlError = error => typeof error !== "object" ? error : JSON.stringify(error);

Utils.decodeIntlError = (error, options = {stripped: false}) => {
  try {
    // do we get the error as a string or as an error object?
    let strippedError = typeof error === 'string' ? error : error.message;

    // if the error hasn't been cleaned before (ex: it's not an error from a form)
    if (!options.stripped) {
      // strip the "GraphQL Error: message [error_code]" given by Apollo if present
      const graphqlPrefixIsPresent = strippedError.match(/GraphQL error: (.*)/);
      if (graphqlPrefixIsPresent) {
        strippedError = graphqlPrefixIsPresent[1];
      }

      // strip the error code if present
      const errorCodeIsPresent = strippedError.match(/(.*)\[(.*)\]/);
      if (errorCodeIsPresent) {
        strippedError = errorCodeIsPresent[1];
      }
    }

    // the error is an object internationalizable
    const parsedError = JSON.parse(strippedError);

    // check if the error has at least an 'id' expected by react-intl
    if (!parsedError.id) {
      console.error('[Undecodable error]', error); // eslint-disable-line
      return {id: 'app.something_bad_happened', value: '[undecodable error]'}
    }

    // return the parsed error
    return parsedError;
  } catch(__) {
    // the error is not internationalizable
    return error;
  }
};

Utils.findWhere = (array, criteria) => array.find(item => Object.keys(criteria).every(key => item[key] === criteria[key]));

Utils.defineName = (o, name) => {
  Object.defineProperty(o, 'name', { value: name });
  return o;
};

Utils.performCheck = (operation, user, checkedObject, context, documentId) => {

  if (!checkedObject) {
    throw new Error(Utils.encodeIntlError({id: `app.document_not_found`, value: documentId}))
  }

  if (!operation(user, checkedObject, context)) {
    throw new Error(Utils.encodeIntlError({id: `app.operation_not_allowed`, value: operation.name}));
  }

}<commit_msg>compactObject now removes empty arrays too<commit_after>/*

Utilities

*/

import marked from 'marked';
import urlObject from 'url';
import moment from 'moment';
import sanitizeHtml from 'sanitize-html';
import getSlug from 'speakingurl';
import { getSetting } from './settings.js';

/**
 * @summary The global namespace for Vulcan utils.
 * @namespace Telescope.utils
 */
export const Utils = {};

/**
 * @summary Convert a camelCase string to dash-separated string
 * @param {String} str
 */
Utils.camelToDash = function (str) {
  return str.replace(/\W+/g, '-').replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase();
};

/**
 * @summary Convert a camelCase string to a space-separated capitalized string
 * See http://stackoverflow.com/questions/4149276/javascript-camelcase-to-regular-form
 * @param {String} str
 */
Utils.camelToSpaces = function (str) {
  return str.replace(/([A-Z])/g, ' $1').replace(/^./, function(str){ return str.toUpperCase(); });
};

/**
 * @summary Convert an underscore-separated string to dash-separated string
 * @param {String} str
 */
Utils.underscoreToDash = function (str) {
  return str.replace('_', '-');
};

/**
 * @summary Convert a dash separated string to camelCase.
 * @param {String} str
 */
Utils.dashToCamel = function (str) {
  return str.replace(/(\-[a-z])/g, function($1){return $1.toUpperCase().replace('-','');});
};

/**
 * @summary Convert a string to camelCase and remove spaces.
 * @param {String} str
 */
Utils.camelCaseify = function(str) {
  str = this.dashToCamel(str.replace(' ', '-'));
  str = str.slice(0,1).toLowerCase() + str.slice(1);
  return str;
};

/**
 * @summary Trim a sentence to a specified amount of words and append an ellipsis.
 * @param {String} s - Sentence to trim.
 * @param {Number} numWords - Number of words to trim sentence to.
 */
Utils.trimWords = function(s, numWords) {

  if (!s)
    return s;

  var expString = s.split(/\s+/,numWords);
  if(expString.length >= numWords)
    return expString.join(" ")+"…";
  return s;
};

/**
 * @summary Trim a block of HTML code to get a clean text excerpt
 * @param {String} html - HTML to trim.
 */
Utils.trimHTML = function (html, numWords) {
  var text = Utils.stripHTML(html);
  return Utils.trimWords(text, numWords);
};

/**
 * @summary Capitalize a string.
 * @param {String} str
 */
Utils.capitalize = function(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
};

Utils.t = function(message) {
  var d = new Date();
  console.log("### "+message+" rendered at "+d.getHours()+":"+d.getMinutes()+":"+d.getSeconds()); // eslint-disable-line
};

Utils.nl2br = function(str) {
  var breakTag = '
';
  return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ breakTag +'$2');
};

Utils.scrollPageTo = function(selector) {
  $('body').scrollTop($(selector).offset().top);
};

Utils.getDateRange = function(pageNumber) {
  var now = moment(new Date());
  var dayToDisplay=now.subtract(pageNumber-1, 'days');
  var range={};
  range.start = dayToDisplay.startOf('day').valueOf();
  range.end = dayToDisplay.endOf('day').valueOf();
  // console.log("after: ", dayToDisplay.startOf('day').format("dddd, MMMM Do YYYY, h:mm:ss a"));
  // console.log("before: ", dayToDisplay.endOf('day').format("dddd, MMMM Do YYYY, h:mm:ss a"));
  return range;
};

//////////////////////////
// URL Helper Functions //
//////////////////////////

/**
 * @summary Returns the user defined site URL or Meteor.absoluteUrl
 */
Utils.getSiteUrl = function () {
  return getSetting('siteUrl', Meteor.absoluteUrl());
};

/**
 * @summary The global namespace for Vulcan utils.
 * @param {String} url - the URL to redirect
 */
Utils.getOutgoingUrl = function (url) {
  return Utils.getSiteUrl() + "out?url=" + encodeURIComponent(url);
};

Utils.slugify = function (s) {
  var slug = getSlug(s, {
    truncate: 60
  });

  // can't have posts with an "edit" slug
  if (slug === "edit") {
    slug = "edit-1";
  }

  return slug;
};

Utils.getUnusedSlug = function (collection, slug) {
  let suffix = "";
  let index = 0;

  // test if slug is already in use
  while (!!collection.findOne({slug: slug+suffix})) {
    index++;
    suffix = "-"+index;
  }

  return slug+suffix;
};

Utils.getShortUrl = function(post) {
  return post.shortUrl || post.url;
};

Utils.getDomain = function(url) {
  try {
    return urlObject.parse(url).hostname.replace('www.', '');
  } catch (error) {
    return null;
  }
};

Utils.invitesEnabled = function() {
  return getSetting("requireViewInvite") || getSetting("requirePostInvite");
};

// add http: if missing
Utils.addHttp = function (url) {
  try {
    if (url.substring(0, 5) !== "http:" && url.substring(0, 6) !== "https:") {
      url = "http:"+url;
    }
    return url;
  } catch (error) {
    return null;
  }
};

/////////////////////////////
// String Helper Functions //
/////////////////////////////

Utils.cleanUp = function(s) {
  return this.stripHTML(s);
};

Utils.sanitize = function(s) {
  // console.log('// before sanitization:')
  // console.log(s)
  if(Meteor.isServer){
    s = sanitizeHtml(s, {
      allowedTags: [
        'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul',
        'ol', 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike',
        'code', 'hr', 'br', 'div', 'table', 'thead', 'caption',
        'tbody', 'tr', 'th', 'td', 'pre', 'img'
      ]
    });
    // console.log('// after sanitization:')
    // console.log(s)
  }
  return s;
};

Utils.stripHTML = function(s) {
  return s.replace(/<(?:.|\n)*?>/gm, '');
};

Utils.stripMarkdown = function(s) {
  var htmlBody = marked(s);
  return Utils.stripHTML(htmlBody);
};

// http://stackoverflow.com/questions/2631001/javascript-test-for-existence-of-nested-object-key
Utils.checkNested = function(obj /*, level1, level2, ... levelN*/) {
  var args = Array.prototype.slice.call(arguments);
  obj = args.shift();

  for (var i = 0; i < args.length; i++) {
    if (!obj.hasOwnProperty(args[i])) {
      return false;
    }
    obj = obj[args[i]];
  }
  return true;
};

Utils.log = function (s) {
  if(getSetting('debug', false) || process.env.NODE_ENV === "development") {
    console.log(s); // eslint-disable-line
  }
};

// see http://stackoverflow.com/questions/8051975/access-object-child-properties-using-a-dot-notation-string
Utils.getNestedProperty = function (obj, desc) {
  var arr = desc.split(".");
  while(arr.length && (obj = obj[arr.shift()]));
  return obj;
};

// see http://stackoverflow.com/a/14058408/649299
_.mixin({
  compactObject : function(object) {
    var clone = _.clone(object);
    _.each(clone, function(value, key) {
      /*

        Remove a value if:
        1. it's not a boolean
        2. it's not a number
        3. it's undefined
        4. it's an empty string
        5. it's null
        6. it's an empty array

      */
      if (typeof value === 'boolean' || typeof value === 'number') {
        return
      }

      if(value === undefined || value === null || value === '' || (Array.isArray(value) && value.length === 0)) {
        delete clone[key];
      }
    });
    return clone;
  }
});

Utils.getFieldLabel = (fieldName, collection) => {
  const label = collection.simpleSchema()._schema[fieldName].label;
  const nameWithSpaces = Utils.camelToSpaces(fieldName);
  return label || nameWithSpaces;
}

Utils.getLogoUrl = () => {
  const logoUrl = getSetting("logoUrl");
  if (!!logoUrl) {
    const prefix = Utils.getSiteUrl().slice(0,-1);
    // the logo may be hosted on another website
    return logoUrl.indexOf('://') > -1 ? logoUrl : prefix + logoUrl;
  }
};

// note(apollo): get collection's name from __typename given by react-apollo
Utils.getCollectionNameFromTypename = (type) => {
  if (type.indexOf('Post') > -1) {
    return 'posts';
  } else if (type.indexOf('Cat') > -1) {
    return 'categories';
  } else if (type.indexOf('User') > -1) {
    return 'users';
  } else if (type.indexOf('Comment') > -1) {
    return 'comments';
  }
};

Utils.findIndex = (array, predicate) => {
  let index = -1;
  let continueLoop = true;
  array.forEach((item, currentIndex) => {
    if (continueLoop && predicate(item)) {
      index = currentIndex
      continueLoop = false
    }
  });
  return index;
}

// adapted from http://stackoverflow.com/a/22072374/649299
Utils.unflatten = function(array, options, parent, level=0, tree){

  const {
    idProperty = '_id',
    parentIdProperty = 'parentId',
    childrenProperty = 'childrenResults'
  } = options;

  level++;

  tree = typeof tree !== "undefined" ? tree : [];

  let children = [];

  if (typeof parent === "undefined") {
    // if there is no parent, we're at the root level
    // so we return all root nodes (i.e. nodes with no parent)
    children = _.filter(array, node => !node[parentIdProperty]);
  } else {
    // if there *is* a parent, we return all its child nodes
    // (i.e. nodes whose parentId is equal to the parent's id.)
    children = _.filter(array, node => node[parentIdProperty] === parent[idProperty]);
  }

  // if we found children, we keep on iterating
  if (!!children.length) {

    if (typeof parent === "undefined") {
      // if we're at the root, then the tree consist of all root nodes
      tree = children;
    } else {
      // else, we add the children to the parent as the "childrenResults" property
      parent[childrenProperty] = children;
    }

    // we call the function on each child
    children.forEach(child => {
      child.level = level;
      Utils.unflatten(array, options, child, level);
    });
  }

  return tree;
};

// remove the telescope object from a schema and duplicate it at the root
Utils.stripTelescopeNamespace = (schema) => {
  // grab the users schema keys
  const schemaKeys = Object.keys(schema);

  // remove any field beginning by telescope: .telescope, .telescope.upvotedPosts.$, ...
  const filteredSchemaKeys = schemaKeys.filter(key => key.slice(0,9) !== 'telescope');

  // replace the previous schema by an object based on this filteredSchemaKeys
  return filteredSchemaKeys.reduce((sch, key) => ({...sch, [key]: schema[key]}), {});
}

/**
 * Convert an array of field names into a Mongo fields specifier
 * @param {Array} fieldsArray
 */
Utils.arrayToFields = (fieldsArray) => {
  return _.object(fieldsArray, _.map(fieldsArray, function () {return true}));
}

/**
 * Get the display name of a React component
 * @param {React Component} WrappedComponent
 */
Utils.getComponentDisplayName = (WrappedComponent) => {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
};

/**
 * Take a collection and a list of documents, and convert all their date fields to date objects
 * This is necessary because Apollo doesn't support custom scalars, and stores dates as strings
 * @param {Object} collection
 * @param {Array} list
 */
Utils.convertDates = (collection, listOrDocument) => {
  // if undefined, just return
  if (!listOrDocument || !listOrDocument.length) return listOrDocument;

  const list = Array.isArray(listOrDocument) ? listOrDocument : [listOrDocument];
  const schema = collection.simpleSchema()._schema;
  const dateFields = _.filter(_.keys(schema), fieldName => schema[fieldName].type === Date);
  const convertedList = list.map(result => {
    dateFields.forEach(fieldName => {
      if (result[fieldName] && typeof result[fieldName] === 'string') {
        result[fieldName] = new Date(result[fieldName]);
      }
    });
    return result;
  });

  return Array.isArray(listOrDocument) ? convertedList : convertedList[0];
}

Utils.encodeIntlError = error => typeof error !== "object" ? error : JSON.stringify(error);

Utils.decodeIntlError = (error, options = {stripped: false}) => {
  try {
    // do we get the error as a string or as an error object?
    let strippedError = typeof error === 'string' ? error : error.message;

    // if the error hasn't been cleaned before (ex: it's not an error from a form)
    if (!options.stripped) {
      // strip the "GraphQL Error: message [error_code]" given by Apollo if present
      const graphqlPrefixIsPresent = strippedError.match(/GraphQL error: (.*)/);
      if (graphqlPrefixIsPresent) {
        strippedError = graphqlPrefixIsPresent[1];
      }

      // strip the error code if present
      const errorCodeIsPresent = strippedError.match(/(.*)\[(.*)\]/);
      if (errorCodeIsPresent) {
        strippedError = errorCodeIsPresent[1];
      }
    }

    // the error is an object internationalizable
    const parsedError = JSON.parse(strippedError);

    // check if the error has at least an 'id' expected by react-intl
    if (!parsedError.id) {
      console.error('[Undecodable error]', error); // eslint-disable-line
      return {id: 'app.something_bad_happened', value: '[undecodable error]'}
    }

    // return the parsed error
    return parsedError;
  } catch(__) {
    // the error is not internationalizable
    return error;
  }
};

Utils.findWhere = (array, criteria) => array.find(item => Object.keys(criteria).every(key => item[key] === criteria[key]));

Utils.defineName = (o, name) => {
  Object.defineProperty(o, 'name', { value: name });
  return o;
};

Utils.performCheck = (operation, user, checkedObject, context, documentId) => {

  if (!checkedObject) {
    throw new Error(Utils.encodeIntlError({id: `app.document_not_found`, value: documentId}))
  }

  if (!operation(user, checkedObject, context)) {
    throw new Error(Utils.encodeIntlError({id: `app.operation_not_allowed`, value: operation.name}));
  }

}<|endoftext|>


<commit_before>var pause = undefined;
var play = undefined;
var video = undefined;
var canvas = undefined;
var tapesound = undefined;
var staticsound = undefined;

function endstatic()
{
	document.body.style.backgroundImage = "none";
	document.body.style.backgroundColor = "black";

	staticsound.pause();
}

function videoloop()
{
		if (video && !video.paused && !video.ended) {
				context.drawImage(video, 0, 0, canvas.width, canvas.height);
				requestAnimationFrame(videoloop);
		}
}

function startvid()
{
	canvas.style.display = "inline";
	video.play();
}

function playoff()
{
	play.style.display = "none";
}
function houseaway()
{
Sky.style.display = "none";
TopHouse.style.display = "none";
Door.style.display = "none";
}
function startsound()
{
	tapesound.play();
}

function onplay(e)
{
	var TopHouse = document.getElementById("TopHouse");
	var Sky = document.getElementById("Sky");
	var Door = document.getElementById("Door");
	pause.style.display = "none";
	play.style.display = "inline";
   	Sky.classList.add("wakethefup");
   	TopHouse.classList.add("wakethefup");
	TopHouse.classList.add("hmmyesthisfloorismade");
	Door.classList.add("wakethefup");
	Door.classList.add("hmmyesthisfloorismade");
// Sandboxing door tweens, they will need to be converted to be used alongside other css rules.
// Apply class at your own risk
//	Door.classList.add("openup");
//
	Door.style.zIndex = "100";
	setTimeout(playoff, 7000);
	setTimeout(houseaway, 12000);
	setTimeout(startvid, 5000);
	setTimeout(endstatic, 2000);
	setTimeout(startsound, 1000);
}

function reset()
{
	pause.style.display = "inline";
	play.style.display = "none";
	canvas.style.display = "none";

	video.currentTime = 0;
	video.pause();

	tapesound.currentTime = 0;
	tapesound.pause();

 	staticsound.play();

	var string = "/img/tiled static.gif";
	document.body.style.backgroundImage = "url('" + string + "')";
	document.body.style.backgroundColor = "white";
}

window.onload = function()
{
	pause = document.getElementById("pause");
	play = document.getElementById("play");
	console.log("load successed");
	pause.onclick = onplay;

	tapesound = document.createElement("audio");
	tapesound.src = "/tapefeed.mp3";
	tapesound.pause();

	staticsound = document.createElement("audio");
	staticsound.src = "https://omaniamnotgoodwith.computer/audio/OurMoreMuffledHouse.mp3";
	staticsound.loop = true;
	staticsound.play();

	video = document.createElement("video");
	video.src = "https://oman.imjake.me/OurHouse.mp4";
	video.pause();
	video.addEventListener('play',function(){
		videoloop();
	},false);
	video.addEventListener('ended',function(){
		reset();
	},false);

	canvas = document.getElementById("canvas");
	context = canvas.getContext("2d");

	canvas.width = canvas.clientWidth;
	canvas.height = canvas.clientHeight;

	context.clearRect(0, 0, canvas.width, canvas.height);
}
<commit_msg>Prep for final transition<commit_after>var pause = undefined;
var play = undefined;
var video = undefined;
var canvas = undefined;
var tapesound = undefined;
var staticsound = undefined;

function endstatic()
{
	document.body.style.backgroundImage = "none";
	document.body.style.backgroundColor = "black";

	staticsound.pause();
}

function videoloop()
{
		if (video && !video.paused && !video.ended) {
				context.drawImage(video, 0, 0, canvas.width, canvas.height);
				requestAnimationFrame(videoloop);
		}
}

function startvid()
{
	canvas.style.display = "inline";
	video.play();
}

function playoff()
{
	play.style.display = "none";
}
function houseaway()
{
Sky.style.display = "none";
TopHouse.style.display = "none";
Door.style.display = "none";
}
function finaltrans()
{Door.classList.add("openup");
}
function startsound()
{
	tapesound.play();
}

function onplay(e)
{
	var TopHouse = document.getElementById("TopHouse");
	var Sky = document.getElementById("Sky");
	var Door = document.getElementById("Door");
	pause.style.display = "none";
	play.style.display = "inline";
   	Sky.classList.add("wakethefup");
   	TopHouse.classList.add("wakethefup");
	TopHouse.classList.add("hmmyesthisfloorismade");
	Door.classList.add("wakethefup");
	Door.classList.add("hmmyesthisfloorismade");
// Sandboxing door tweens, they will need to be converted to be used alongside other css rules.
// Apply class at your own risk
//	Door.classList.add("openup");
//
	Door.style.zIndex = "100";
	setTimeout(playoff, 7000);
	setTimeout(houseaway, 12000);
	setTimeout(finaltrans, 9000);
	setTimeout(startvid, 5000);
	setTimeout(endstatic, 2000);
	setTimeout(startsound, 1000);
}

function reset()
{
	pause.style.display = "inline";
	play.style.display = "none";
	canvas.style.display = "none";

	video.currentTime = 0;
	video.pause();

	tapesound.currentTime = 0;
	tapesound.pause();

 	staticsound.play();

	var string = "/img/tiled static.gif";
	document.body.style.backgroundImage = "url('" + string + "')";
	document.body.style.backgroundColor = "white";
}

window.onload = function()
{
	pause = document.getElementById("pause");
	play = document.getElementById("play");
	console.log("load successed");
	pause.onclick = onplay;

	tapesound = document.createElement("audio");
	tapesound.src = "/tapefeed.mp3";
	tapesound.pause();

	staticsound = document.createElement("audio");
	staticsound.src = "https://omaniamnotgoodwith.computer/audio/OurMoreMuffledHouse.mp3";
	staticsound.loop = true;
	staticsound.play();

	video = document.createElement("video");
	video.src = "https://oman.imjake.me/OurHouse.mp4";
	video.pause();
	video.addEventListener('play',function(){
		videoloop();
	},false);
	video.addEventListener('ended',function(){
		reset();
	},false);

	canvas = document.getElementById("canvas");
	context = canvas.getContext("2d");

	canvas.width = canvas.clientWidth;
	canvas.height = canvas.clientHeight;

	context.clearRect(0, 0, canvas.width, canvas.height);
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import Toolbar, { Button } from './toolbar';
import { Icon, Menu } from 'antd';
import { hasMark, hasBlock } from './utils/has';
import addBlock from './utils/add-block';

export default class ToolbarText extends Component {
  onClickBlock = (e, props) => {
    const { state, onChange, blockTypes } = this.props;
    e.preventDefault();
    onChange(addBlock(state, props, blockTypes));
  };
  renderBlockButton = props => this.renderOptionButton(props, hasBlock, this.onClickBlock);
  onClickMark = (e, type) => {
    e.stopPropagation();
    e.preventDefault();
    const { state, onChange } = this.props;
    onChange(state.change().toggleMark(type));
  };
  renderMarkButton = props => this.renderOptionButton(props, hasMark, this.onClickMark);
  renderActionButton = (props) => {
    const isActive = props.isActive ? props.isActive(this.props) : false;
    const isActiveFn = () => isActive;
    const fn = e => props.onClick(this.props, isActive, e);

    return this.renderOptionButton(props, isActiveFn, fn);
  };
  renderOptionButton = (props, isActiveFn, onMouseDownFn, label) => {
    const { state } = this.props;
    const { type } = props;
    const isActive = isActiveFn(state, type);
    const onMouseDown = e => onMouseDownFn(e, props);

    const icon = label || props.label || <Icon type={props.icon} />;
    if (type && Array.isArray(type)) {
      return (
        <Menu.SubMenu key={type.join('-')} title={<Button>{icon}</Button>}>
          {type.map((subType, index) => {
            const subLabel =
              props.description && props.description[index]
                ? props.description[index]
                : label || subType;

            return this.renderOptionButton(
              { ...props, type: subType },
              isActiveFn,
              Array.isArray(onMouseDownFn) ? onMouseDownFn[index] : onMouseDownFn,
              subLabel,
            );
          })}
        </Menu.SubMenu>
      );
    }

    return (
      <Menu.Item key={type} className={isActive && 'ant-menu-item-selected'}>
        <Button onMouseDown={onMouseDown}>{icon}</Button>
      </Menu.Item>
    );
  };
  onOpen = ({ firstChild: menu }) => {
    this.setState({ menu });
  };
  render() {
    const { state, toolbarMarks, toolbarTypes, toolbarActions, show } = this.props;
    const display = !state.isBlurred && !state.isCollapsed;

    return (
      <Toolbar isOpened={!!display} show={show}>
        {toolbarMarks.map(this.renderMarkButton)}
        {toolbarTypes.map(this.renderBlockButton)}
        {toolbarActions.map(this.renderActionButton)}
      </Toolbar>
    );
  }
}
<commit_msg>fix change block type<commit_after>import React, { Component } from 'react';
import Toolbar, { Button } from './toolbar';
import { Icon, Menu } from 'antd';
import { hasMark, hasBlock } from './utils/has';

export default class ToolbarText extends Component {
  onClickBlock = (e, props) => {
    const { state, onChange } = this.props;
    e.preventDefault();
    onChange(state.change().setBlock(props.type));
  };
  renderBlockButton = props => this.renderOptionButton(props, hasBlock, this.onClickBlock);
  onClickMark = (e, type) => {
    e.stopPropagation();
    e.preventDefault();
    const { state, onChange } = this.props;
    onChange(state.change().toggleMark(type));
  };
  renderMarkButton = props => this.renderOptionButton(props, hasMark, this.onClickMark);
  renderActionButton = (props) => {
    const isActive = props.isActive ? props.isActive(this.props) : false;
    const isActiveFn = () => isActive;
    const fn = e => props.onClick(this.props, isActive, e);

    return this.renderOptionButton(props, isActiveFn, fn);
  };
  renderOptionButton = (props, isActiveFn, onMouseDownFn, label) => {
    const { state } = this.props;
    const { type } = props;
    const isActive = isActiveFn(state, type);
    const onMouseDown = e => onMouseDownFn(e, props);

    const icon = label || props.label || <Icon type={props.icon} />;
    if (type && Array.isArray(type)) {
      return (
        <Menu.SubMenu key={type.join('-')} title={<Button>{icon}</Button>}>
          {type.map((subType, index) => {
            const subLabel =
              props.description && props.description[index]
                ? props.description[index]
                : label || subType;

            return this.renderOptionButton(
              { ...props, type: subType },
              isActiveFn,
              Array.isArray(onMouseDownFn) ? onMouseDownFn[index] : onMouseDownFn,
              subLabel,
            );
          })}
        </Menu.SubMenu>
      );
    }

    return (
      <Menu.Item key={type} className={isActive && 'ant-menu-item-selected'}>
        <Button onMouseDown={onMouseDown}>{icon}</Button>
      </Menu.Item>
    );
  };
  onOpen = ({ firstChild: menu }) => {
    this.setState({ menu });
  };
  render() {
    const { state, toolbarMarks, toolbarTypes, toolbarActions, show } = this.props;
    const display = !state.isBlurred && !state.isCollapsed;

    return (
      <Toolbar isOpened={!!display} show={show}>
        {toolbarMarks.map(this.renderMarkButton)}
        {toolbarTypes.map(this.renderBlockButton)}
        {toolbarActions.map(this.renderActionButton)}
      </Toolbar>
    );
  }
}
<|endoftext|>


<commit_before>import moment from 'moment'
import fs from 'fs-extra'
import logger from 'winston'
import makeDebug from 'debug'
import { getItems, replaceItems, discard } from 'feathers-hooks-common'
import { Grid } from '../grid'

const debug = makeDebug('weacast:weacast-core')
const discardDataField = discard('data')
const discardFilepathField = discard('filePath')
const discardConvertedFilepathField = discard('convertedFilePath')

export function marshallQuery (hook) {
  let query = hook.params.query
  if (query) {
      // Need to convert from client/server side types : string or moment dates
    if (typeof query.runTime === 'string') {
      query.runTime = new Date(query.runTime)
    } else if (moment.isMoment(query.runTime)) {
      query.runTime = new Date(query.runTime.format())
    }
    if (typeof query.forecastTime === 'string') {
      query.forecastTime = new Date(query.forecastTime)
    } else if (moment.isMoment(query.forecastTime)) {
      query.forecastTime = new Date(query.forecastTime.format())
    }
      // In this case take care that we always internally require the file path, it will be removed for the client by another hook
    if (query.$select && this.element.dataStore === 'fs') {
      query.$select.push('convertedFilePath')
    }
    // When listing available forecast we might want to disable pagination
    // However disabling or changing the default pagination is not available in the client in Feathers by default,
    // this is the reason of this specific hook
    if (query.$paginate === 'false' || query.$paginate === false) {
      hook.params.paginate = false
      delete query.$paginate
    }
    // Resampling is used by hooks only, do not send it to DB
    if (query.oLon && query.oLat && query.sLon && query.sLat && query.dLon && query.dLat) {
      // convert when required from query strings
      hook.params.oLat = Number.isFinite(query.oLat) ? query.oLat : parseFloat(query.oLat)
      hook.params.oLon = Number.isFinite(query.oLon) ? query.oLon : parseFloat(query.oLon)
      hook.params.sLat = Number.isFinite(query.sLat) ? query.sLat : parseFloat(query.sLat)
      hook.params.sLon = Number.isFinite(query.sLon) ? query.sLon : parseFloat(query.sLon)
      hook.params.dLat = Number.isFinite(query.dLat) ? query.dLat : parseFloat(query.dLat)
      hook.params.dLon = Number.isFinite(query.dLon) ? query.dLon : parseFloat(query.dLon)
      delete query.oLat
      delete query.oLon
      delete query.sLat
      delete query.sLon
      delete query.dLat
      delete query.dLon
    }
  }
}

export function processForecastTime (hook) {
  let query = hook.params.query
  if (query && query.time) {
      // Find nearest forecast time corresponding to request time
    let time = (typeof query.time === 'string' ? moment.utc(query.time) : query.time)
    let forecastTime = hook.service.getNearestForecastTime(time)
    delete query.time
    query.forecastTime = new Date(forecastTime.format())
  }
  if (query && query.from && query.to) {
      // Find nearest forecast time corresponding to request time range
    let from = (typeof query.from === 'string' ? moment.utc(query.from) : query.from)
    let to = (typeof query.to === 'string' ? moment.utc(query.to) : query.to)
    let fromForecastTime = hook.service.getNearestForecastTime(from)
    let toForecastTime = hook.service.getNearestForecastTime(to)
    delete query.from
    delete query.to
    query.forecastTime = {
      $gte: new Date(fromForecastTime.format()),
      $lte: new Date(toForecastTime.format())
    }
  }
}

export function processData (hook) {
  let params = hook.params
  let query = params.query
  let items = getItems(hook)
  const isArray = Array.isArray(items)
  items = (isArray ? items : [items])

    // If we use a file based storage we have to load data on demand
  if (this.element.dataStore === 'fs') {
      // Process data files when required
    if (query && query.$select && query.$select.includes('data')) {
      return new Promise((resolve, reject) => {
        let dataPromises = []
        items.forEach(item => {
          dataPromises.push(
            fs.readJson(item.convertedFilePath, 'utf8')
            .then(grid => {
              item.data = grid
              return item
            })
            .catch(error => {
              let errorMessage = 'Cannot read converted ' + this.forecast.name + '/' + this.element.name + ' forecast'
              if (item.forecastTime) errorMessage += ' at ' + item.forecastTime.format()
              if (item.runTime) errorMessage += ' for run ' + item.runTime.format()
              logger.error(errorMessage)
              debug('Input JSON file was : ' + item.convertedFilePath)
              reject(error)
            })
          )
        })

        Promise.all(dataPromises).then(_ => {
          // Remove as well any sensitive information about file path on the client side
          // Must be done second as we need this information first to read data
          discardFilepathField(hook)
          discardConvertedFilepathField(hook)
          replaceItems(hook, isArray ? items : items[0])
          resolve(hook)
        })
      })
    } else {
      // Remove any sensitive information about file path on the client side
      discardFilepathField(hook)
      discardConvertedFilepathField(hook)
    }
  } else {
    // Only discard if not explicitely asked by $select
    if (!query || !query.$select || !query.$select.includes('data')) {
      discardDataField(hook)
    } else {
      // Check for resampling on returned data
      if (params.oLon && params.oLat && params.sLon && params.sLat && params.dLon && params.dLat) {
        items.forEach(item => {
          let grid = new Grid({
            bounds: hook.service.forecast.bounds,
            origin: hook.service.forecast.origin,
            size: hook.service.forecast.size,
            resolution: hook.service.forecast.resolution,
            data: item.data
          })
          item.data = grid.resample([params.oLon, params.oLat], [params.dLon, params.dLat], [params.sLon, params.sLat])
        })
        replaceItems(hook, isArray ? items : items[0])
      }
    }
  }
}
<commit_msg>Fixed bug<commit_after>import moment from 'moment'
import fs from 'fs-extra'
import logger from 'winston'
import makeDebug from 'debug'
import { getItems, replaceItems, discard } from 'feathers-hooks-common'
import { Grid } from '../grid'

const debug = makeDebug('weacast:weacast-core')
const discardDataField = discard('data')
const discardFilepathField = discard('filePath')
const discardConvertedFilepathField = discard('convertedFilePath')

export function marshallQuery (hook) {
  let query = hook.params.query
  if (query) {
      // Need to convert from client/server side types : string or moment dates
    if (typeof query.runTime === 'string') {
      query.runTime = new Date(query.runTime)
    } else if (moment.isMoment(query.runTime)) {
      query.runTime = new Date(query.runTime.format())
    }
    if (typeof query.forecastTime === 'string') {
      query.forecastTime = new Date(query.forecastTime)
    } else if (moment.isMoment(query.forecastTime)) {
      query.forecastTime = new Date(query.forecastTime.format())
    }
      // In this case take care that we always internally require the file path, it will be removed for the client by another hook
    if (query.$select && this.element && this.element.dataStore === 'fs') {
      query.$select.push('convertedFilePath')
    }
    // When listing available forecast we might want to disable pagination
    // However disabling or changing the default pagination is not available in the client in Feathers by default,
    // this is the reason of this specific hook
    if (query.$paginate === 'false' || query.$paginate === false) {
      hook.params.paginate = false
      delete query.$paginate
    }
    // Resampling is used by hooks only, do not send it to DB
    if (query.oLon && query.oLat && query.sLon && query.sLat && query.dLon && query.dLat) {
      // convert when required from query strings
      hook.params.oLat = Number.isFinite(query.oLat) ? query.oLat : parseFloat(query.oLat)
      hook.params.oLon = Number.isFinite(query.oLon) ? query.oLon : parseFloat(query.oLon)
      hook.params.sLat = Number.isFinite(query.sLat) ? query.sLat : parseFloat(query.sLat)
      hook.params.sLon = Number.isFinite(query.sLon) ? query.sLon : parseFloat(query.sLon)
      hook.params.dLat = Number.isFinite(query.dLat) ? query.dLat : parseFloat(query.dLat)
      hook.params.dLon = Number.isFinite(query.dLon) ? query.dLon : parseFloat(query.dLon)
      delete query.oLat
      delete query.oLon
      delete query.sLat
      delete query.sLon
      delete query.dLat
      delete query.dLon
    }
  }
}

export function processForecastTime (hook) {
  let query = hook.params.query
  if (query && query.time) {
      // Find nearest forecast time corresponding to request time
    let time = (typeof query.time === 'string' ? moment.utc(query.time) : query.time)
    let forecastTime = hook.service.getNearestForecastTime(time)
    delete query.time
    query.forecastTime = new Date(forecastTime.format())
  }
  if (query && query.from && query.to) {
      // Find nearest forecast time corresponding to request time range
    let from = (typeof query.from === 'string' ? moment.utc(query.from) : query.from)
    let to = (typeof query.to === 'string' ? moment.utc(query.to) : query.to)
    let fromForecastTime = hook.service.getNearestForecastTime(from)
    let toForecastTime = hook.service.getNearestForecastTime(to)
    delete query.from
    delete query.to
    query.forecastTime = {
      $gte: new Date(fromForecastTime.format()),
      $lte: new Date(toForecastTime.format())
    }
  }
}

export function processData (hook) {
  let params = hook.params
  let query = params.query
  let items = getItems(hook)
  const isArray = Array.isArray(items)
  items = (isArray ? items : [items])

    // If we use a file based storage we have to load data on demand
  if (this.element.dataStore === 'fs') {
      // Process data files when required
    if (query && query.$select && query.$select.includes('data')) {
      return new Promise((resolve, reject) => {
        let dataPromises = []
        items.forEach(item => {
          dataPromises.push(
            fs.readJson(item.convertedFilePath, 'utf8')
            .then(grid => {
              item.data = grid
              return item
            })
            .catch(error => {
              let errorMessage = 'Cannot read converted ' + this.forecast.name + '/' + this.element.name + ' forecast'
              if (item.forecastTime) errorMessage += ' at ' + item.forecastTime.format()
              if (item.runTime) errorMessage += ' for run ' + item.runTime.format()
              logger.error(errorMessage)
              debug('Input JSON file was : ' + item.convertedFilePath)
              reject(error)
            })
          )
        })

        Promise.all(dataPromises).then(_ => {
          // Remove as well any sensitive information about file path on the client side
          // Must be done second as we need this information first to read data
          discardFilepathField(hook)
          discardConvertedFilepathField(hook)
          replaceItems(hook, isArray ? items : items[0])
          resolve(hook)
        })
      })
    } else {
      // Remove any sensitive information about file path on the client side
      discardFilepathField(hook)
      discardConvertedFilepathField(hook)
    }
  } else {
    // Only discard if not explicitely asked by $select
    if (!query || !query.$select || !query.$select.includes('data')) {
      discardDataField(hook)
    } else {
      // Check for resampling on returned data
      if (params.oLon && params.oLat && params.sLon && params.sLat && params.dLon && params.dLat) {
        items.forEach(item => {
          let grid = new Grid({
            bounds: hook.service.forecast.bounds,
            origin: hook.service.forecast.origin,
            size: hook.service.forecast.size,
            resolution: hook.service.forecast.resolution,
            data: item.data
          })
          item.data = grid.resample([params.oLon, params.oLat], [params.dLon, params.dLat], [params.sLon, params.sLat])
        })
        replaceItems(hook, isArray ? items : items[0])
      }
    }
  }
}
<|endoftext|>


<commit_before>// Load in dependencies
var assert = require('assert');
var fs = require('fs');
var exec = require('child_process').exec;
var async = require('async');
var imageDiff = require('image-diff');
var rimraf = require('rimraf');
var shellQuote = require('shell-quote');
var serverUtils = require('../utils/server');

// Set up common variables
var expectedScreenshots = __dirname + '/expected_screenshots';
var actualScreenshots = __dirname + '/actual_screenshots';
var diffScreenshots = __dirname + '/diff_screenshots';

// Clean up actual screenshots and diffs
rimraf.sync(actualScreenshots);
rimraf.sync(diffScreenshots);

// Ensure the screenshot diff directory exists
fs.mkdirSync(diffScreenshots);

// Set up unabstracted variables
// TODO: Move browsers, urls into standalone files
// TODO: See notes in https://gist.github.com/twolfson/6077989
// TODO: Optimize space via tarballing expected files?
// var browsers = ['phantomjs'];
// DEV: js-yaml is required to make this require work properly
var yml = require('js-yaml');
var urls = require('./urls.yml');
assert(yml); // DEV: We use assert to silence jshint complaints

// Start a server
var server = serverUtils.startServer();

// For each of the URLs
async.map(urls, function (pathname, done) {
  // Screenshot the webpage
  var url = serverUtils.getUrl(pathname);
  var filename = encodeURIComponent(pathname) + '.png';
  var actualImg = actualScreenshots + '/' + filename;
  var phantomJsCmd = shellQuote.quote(['phantomjs', 'phantomjs_scripts/screenshot.js', url, actualImg]);
  exec(phantomJsCmd, {cwd: __dirname}, function processScreenshot (err, stdout, stderr) {
    // If stderr or stdout exist, log them
    if (stderr) { console.log('STDERR: ', stderr); }
    if (stdout) { console.log('STDOUT: ', stdout); }

    // If there is an error, callback with it
    if (err) { return done(err); }

    // TODO: Emit an event instead
    // Notify the user that we have screenshotted successfully
    console.log('Successfully screenshotted ' + url);

    imageDiff({
      actualImage: actualImg,
      expectedImage: expectedScreenshots + '/' + filename,
      diffImage: diffScreenshots + '/' + filename
    }, function handleDiffResult (err, imagesAreSame) {
      // If there was an error, callback with it
      if (err) { return done(err); }

      // Otherwise, callback with result
      done(null, {
        url: url,
        pass: imagesAreSame
      });
    });
  });
}, function (err, results) {
  // Take down server
  server.destroy(function () {
    // If there was an error, log it and leave
    if (err) {
      console.error('ERROR: ', err);
      return process.exit(1);
    }

    // Otherwise, determine if there were any failures
    var failedResults = results.filter(function (result) {
          return !result.pass;
        });

    // If there were failures, log them and leave
    if (failedResults.length > 0) {
      failedResults.forEach(function (result) {
        console.log('Failed result for ' + result.url);
      });
      process.exit(1);
    } else {
    // Otherwise, exit gracefully
      console.log('All done!');
      process.exit(0);
    }
  });
});
<commit_msg>Running node webkit<commit_after>// Load in dependencies
var assert = require('assert');
var fs = require('fs');
var exec = require('child_process').exec;
var async = require('async');
var imageDiff = require('image-diff');
var rimraf = require('rimraf');
var shellQuote = require('shell-quote');
var serverUtils = require('../utils/server');

// Set up common variables
var expectedScreenshots = __dirname + '/expected_screenshots';
var actualScreenshots = __dirname + '/actual_screenshots';
var diffScreenshots = __dirname + '/diff_screenshots';

// Clean up actual screenshots and diffs
rimraf.sync(actualScreenshots);
rimraf.sync(diffScreenshots);

// Ensure the screenshot diff directory exists
fs.mkdirSync(diffScreenshots);

// Set up unabstracted variables
// TODO: Move browsers, urls into standalone files
// TODO: See notes in https://gist.github.com/twolfson/6077989
// TODO: Optimize space via tarballing expected files?
// var browsers = ['phantomjs'];
// DEV: js-yaml is required to make this require work properly
var yml = require('js-yaml');
var urls = require('./urls.yml');
assert(yml); // DEV: We use assert to silence jshint complaints

// Start a server
var server = serverUtils.startServer();

// For each of the URLs
async.map(urls, function (pathname, done) {
  // Screenshot the webpage
  var url = serverUtils.getUrl(pathname);
  var filename = encodeURIComponent(pathname) + '.png';
  var actualImg = actualScreenshots + '/' + filename;
  var screenshotCmd = shellQuote.quote(['nw', '.', url, actualImg]);
  exec(screenshotCmd, {cwd: __dirname + '/node-webkit_scripts/'}, function processScreenshot (err, stdout, stderr) {
    // If stderr or stdout exist, log them
    if (stderr) { console.log('STDERR: ', stderr); }
    if (stdout) { console.log('STDOUT: ', stdout); }

    // If there is an error, callback with it
    if (err) { return done(err); }

    // TODO: Emit an event instead
    // Notify the user that we have screenshotted successfully
    console.log('Successfully screenshotted ' + url);

    imageDiff({
      actualImage: actualImg,
      expectedImage: expectedScreenshots + '/' + filename,
      diffImage: diffScreenshots + '/' + filename
    }, function handleDiffResult (err, imagesAreSame) {
      // If there was an error, callback with it
      if (err) { return done(err); }

      // Otherwise, callback with result
      done(null, {
        url: url,
        pass: imagesAreSame
      });
    });
  });
}, function (err, results) {
  // Take down server
  server.destroy(function () {
    // If there was an error, log it and leave
    if (err) {
      console.error('ERROR: ', err);
      return process.exit(1);
    }

    // Otherwise, determine if there were any failures
    var failedResults = results.filter(function (result) {
          return !result.pass;
        });

    // If there were failures, log them and leave
    if (failedResults.length > 0) {
      failedResults.forEach(function (result) {
        console.log('Failed result for ' + result.url);
      });
      process.exit(1);
    } else {
    // Otherwise, exit gracefully
      console.log('All done!');
      process.exit(0);
    }
  });
});
<|endoftext|>


<commit_before>/**
 *
 * @name LineChart
 * @constructor
 * @classdesc This class constructs a c3 line chart.
 * @memberOf dex.charts.c3
 * @implements {dex/component}
 *
 * @example {@lang javascript}
 * var areachart = new dex.charts.c3.AreaChart({
 *   'parent' : "#AreaChart",
 *   'id'     : "AreaChart"
 *   'csv'    : { header : [ "X", "Y", "Z" ],
 *                data   : [[ 1, 2, 3 ], [4, 5, 6], [7, 8, 9]]}
 * });
 *
 * @param {object} userConfig - A user supplied configuration object which will override the defaults.
 * @param {string} userConfig.parent - The parent node to which this Axis will be attached.  Ex: #MyParent
 * will attach to a node with an id = "MyParent".
 * @param {string} [userConfig.id=Axis] - The id of this axis.
 * @param {string} [userConfig.class=Axis] - The class of this axis.
 * @param {csv} userConfig.csv - The user's CSV data.
 *
 * @inherit module:dex/component
 *
 */

var linechart = function (userConfig) {
    var chart;

    var defaults =
    {
        // The parent container of this chart.
        'parent': '#LineChart',
        // Set these when you need to CSS style components independently.
        'id': 'LineChart',
        'class': 'LineChart',
        'resizable': true,
        'csv': {
            'header': [],
            'data': []
        },
        'linktype' : 'area',
        'width': "100%",
        'height': "100%"
    };

    var chart = new dex.component(userConfig, defaults);
    var internalChart;
    var selectedColumns = [];

    chart.render = function render() {

        //var chart = this;
        var config = chart.config;
        var csv = config.csv;
        window.onresize = this.render;

        if (chart.config.resizable) {
            var width = d3.select(chart.config.parent).property("clientWidth");
            var height = d3.select(chart.config.parent).property("clientHeight");
            dex.console.log(chart.config.id + ": resize(" + width + "," + height + ")");
            chart.attr("width", width).attr("height", height);
        }
        d3.select(config.parent).selectAll("*").remove();
        var gtypes = dex.csv.guessTypes(csv);
        selectedColumns = dex.csv.getNumericIndices(csv);
        if (gtypes[0] == "string")
        {
            selectedColumns.unshift(0);
        }
        var ncsv = dex.csv.columnSlice(csv, selectedColumns);

        var columns = dex.csv.transpose(ncsv);

        for (var ci = 0; ci < columns.header.length; ci++) {
            columns.data[ci].unshift(columns.header[ci]);
        }

        var types = {};
        dex.range(1, ncsv.header.length)
          .map(function(hi) { types[ncsv.header[hi-1]] = config.linktype; });

        var c3config = {
            'bindto' : config.parent,
            'data': {
                'x' : columns.header[0],
                'columns' : columns.data,
                'types' : types
            },
            subchart: {
                show: true
            },
            zoom: {
                enabled: true
            },
            legend: {
                position : 'right'
            },
            groups: config.groups
        };

        if (gtypes[0] == "string")
        {
            c3config["axis"] = {
                "x": {
                    "type": "category",
                    "categories": [].concat.apply([],
                      dex.matrix.uniques(dex.matrix.slice(csv.data, [0]))).sort()
                }
            }
        }

        //dex.console.log("CATEGORIES", c3config);
        internalChart = c3.generate(c3config);
    };

    chart.update = function () {
        var chart = this;
        var config = chart.config;
        var csv = config.csv;

        var gtypes = dex.csv.guessTypes(csv);

        var ncsv = dex.csv.columnSlice(csv, selectedColumns);
        var columns = dex.csv.transpose(ncsv);

        for (var ci = 0; ci < columns.header.length; ci++) {
            columns.data[ci].unshift(columns.header[ci]);
        }

        var types = {};
        dex.range(1, ncsv.header.length)
          .map(function(hi) { types[ncsv.header[hi-1]] = config.linktype; });

        var c3config = {
            'columns' : columns.data
        };

        //internalChart.groups(config.groups);
        internalChart.load(c3config);
    };

    $(document).ready(function () {
        // Make the entire chart draggable.
        //$(chart.config.parent).draggable();
    });

    return chart;
};

module.exports = linechart;<commit_msg>Removed logging<commit_after>/**
 *
 * @name LineChart
 * @constructor
 * @classdesc This class constructs a c3 line chart.
 * @memberOf dex.charts.c3
 * @implements {dex/component}
 *
 * @example {@lang javascript}
 * var areachart = new dex.charts.c3.AreaChart({
 *   'parent' : "#AreaChart",
 *   'id'     : "AreaChart"
 *   'csv'    : { header : [ "X", "Y", "Z" ],
 *                data   : [[ 1, 2, 3 ], [4, 5, 6], [7, 8, 9]]}
 * });
 *
 * @param {object} userConfig - A user supplied configuration object which will override the defaults.
 * @param {string} userConfig.parent - The parent node to which this Axis will be attached.  Ex: #MyParent
 * will attach to a node with an id = "MyParent".
 * @param {string} [userConfig.id=Axis] - The id of this axis.
 * @param {string} [userConfig.class=Axis] - The class of this axis.
 * @param {csv} userConfig.csv - The user's CSV data.
 *
 * @inherit module:dex/component
 *
 */

var linechart = function (userConfig) {
    var chart;

    var defaults =
    {
        // The parent container of this chart.
        'parent': '#LineChart',
        // Set these when you need to CSS style components independently.
        'id': 'LineChart',
        'class': 'LineChart',
        'resizable': true,
        'csv': {
            'header': [],
            'data': []
        },
        'linktype' : 'area',
        'width': "100%",
        'height': "100%"
    };

    var chart = new dex.component(userConfig, defaults);
    var internalChart;
    var selectedColumns = [];

    chart.render = function render() {

        //var chart = this;
        var config = chart.config;
        var csv = config.csv;
        window.onresize = this.render;

        if (chart.config.resizable) {
            var width = d3.select(chart.config.parent).property("clientWidth");
            var height = d3.select(chart.config.parent).property("clientHeight");
            dex.console.log(chart.config.id + ": resize(" + width + "," + height + ")");
            chart.attr("width", width).attr("height", height);
        }
        d3.select(config.parent).selectAll("*").remove();
        var gtypes = dex.csv.guessTypes(csv);
        selectedColumns = dex.csv.getNumericIndices(csv);
        if (gtypes[0] == "string")
        {
            selectedColumns.unshift(0);
        }
        else if (gtypes[0] == "date")
        {
            selectedColumns.unshift(0);
        }

        var ncsv = dex.csv.columnSlice(csv, selectedColumns);

        var columns = dex.csv.transpose(ncsv);

        for (var ci = 0; ci < columns.header.length; ci++) {
            columns.data[ci].unshift(columns.header[ci]);
        }

        var types = {};
        dex.range(1, ncsv.header.length)
          .map(function(hi) { types[ncsv.header[hi-1]] = config.linktype; });

        var c3config = {
            'bindto' : config.parent,
            'data': {
                'x' : columns.header[0],
                'columns' : columns.data,
                'types' : types
            },
            subchart: {
                show: true
            },
            zoom: {
                enabled: true
            },
            legend: {
                position : 'right'
            },
            groups: config.groups
        };

        //dex.console.log("TYPES:", gtypes);

        if (gtypes[0] == "string")
        {
            c3config["axis"] = {
                "x": {
                    "type": "category",
                    "categories": [].concat.apply([],
                      dex.matrix.uniques(dex.matrix.slice(csv.data, [0]))).sort()
                }
            }
        }
        else if (gtypes[0] == "date")
        {
            //dex.console.log("DEALING WITH A DATE...");
            c3config["axis"] = {
                "x": {
                    "type": "timeseries",
                    "tick": {
                        format: '%Y-%m-%d'
                    }
                }
            }
        }

        //dex.console.log("RENDER C3CONFIG", c3config);

        //dex.console.log("CATEGORIES", c3config);
        internalChart = c3.generate(c3config);
    };

    chart.update = function () {
        var chart = this;
        var config = chart.config;
        var csv = config.csv;

        var gtypes = dex.csv.guessTypes(csv);

        var ncsv = dex.csv.columnSlice(csv, selectedColumns);
        var columns = dex.csv.transpose(ncsv);

        for (var ci = 0; ci < columns.header.length; ci++) {
            columns.data[ci].unshift(columns.header[ci]);
        }

        var types = {};
        dex.range(1, ncsv.header.length)
          .map(function(hi) { types[ncsv.header[hi-1]] = config.linktype; });

        var c3config = {
            'columns' : columns.data
        };

        //dex.console.log("C3CONFIG", c3config);

        //internalChart.groups(config.groups);
        internalChart.load(c3config);
    };

    $(document).ready(function () {
        // Make the entire chart draggable.
        //$(chart.config.parent).draggable();
    });

    return chart;
};

module.exports = linechart;<|endoftext|>


<commit_before>import R from 'ramda';
import { Maybe } from 'ramda-fantasy';

import {
  getLinkNodeIds,
  isInputPin,
  isOutputPin,
  PIN_TYPE,
  getPatchPath,
  listLibraryPatches,
  omitPatches,
  getPatchByPath,
} from 'xod-project';

import { getProject } from './selectors';

import { LINK_ERRORS } from '../editor/constants';

// :: NodeId -> PinKey -> RenderableNodes -> RenderablePin
export const getRenderablePin = R.uncurryN(3, (nodeId, pinKey) => R.path([nodeId, 'pins', pinKey]));

// :: NodeId -> Link -> Boolean
export const isLinkConnectedToNode =
  R.uncurryN(2, nodeId =>
    R.compose(
      R.contains(nodeId),
      getLinkNodeIds
    )
  );

//
// pins linking validation utils
//

// :: RenderablePin -> String | Null
export const getPinSelectionError = (pin) => {
  if (isInputPin(pin) && pin.isConnected) {
    return LINK_ERRORS.ONE_LINK_FOR_INPUT_PIN;
  }

  return null;
};

// :: RenderablePin -> Boolean
export const isPinLinkable = R.complement(getPinSelectionError);

// :: RenderablePin -> RenderablePin -> String | Null
export const getLinkingError = R.curry((pin1, pin2) => {
  if (!isPinLinkable(pin1) || !isPinLinkable(pin2)) {
    return LINK_ERRORS.ONE_LINK_FOR_INPUT_PIN;
  }

  // same direction?
  if (pin1.direction === pin2.direction) return LINK_ERRORS.SAME_DIRECTION;

  // very primitive case of a feedback loop
  // TODO: this needs to be much more complicated (and implemented in xod-project)
  if (pin1.nodeId === pin2.nodeId) {
    return LINK_ERRORS.SAME_NODE;
  }

  const inputPin = isInputPin(pin1) ? pin1 : pin2;
  const outputPin = isOutputPin(pin1) ? pin1 : pin2;

  // types compatibility
  if (inputPin.type === PIN_TYPE.PULSE && outputPin.type !== PIN_TYPE.PULSE) {
    return LINK_ERRORS.INCOMPATIBLE_TYPES;
  } else if (
    inputPin.type === PIN_TYPE.STRING &&
    !R.contains(outputPin.type, [PIN_TYPE.BOOLEAN, PIN_TYPE.NUMBER, PIN_TYPE.STRING])
  ) {
    return LINK_ERRORS.INCOMPATIBLE_TYPES;
  } else if (
    R.contains(outputPin.type, [PIN_TYPE.BOOLEAN, PIN_TYPE.NUMBER]) &&
    !R.contains(outputPin.type, [PIN_TYPE.BOOLEAN, PIN_TYPE.NUMBER])
  ) {
    return LINK_ERRORS.INCOMPATIBLE_TYPES;
  }

  return null;
});

// :: RenderablePin -> RenderablePin -> Boolean
export const canPinsBeLinked = R.complement(getLinkingError);

export const getJSONForExport = (project) => {
  const libPaths = R.compose(
    R.map(getPatchPath),
    listLibraryPatches
  )(project);

  return R.compose(
    p => JSON.stringify(p, null, 2),
    omitPatches(libPaths)
  )(project);
};

// :: State -> PatchPath -> Boolean
export const isPatchPathTaken = (state, newPatchPath) => {
  const maybeExistingPatch = R.compose(
    getPatchByPath(newPatchPath),
    getProject
  )(state);

  return Maybe.isJust(maybeExistingPatch);
};
<commit_msg>tweak(xod-client): use rules from xod-projects when detecting pins linkability<commit_after>import R from 'ramda';
import { Maybe } from 'ramda-fantasy';

import {
  getLinkNodeIds,
  isInputPin,
  isOutputPin,
  getPatchPath,
  listLibraryPatches,
  omitPatches,
  getPatchByPath,
  canCastTypes,
} from 'xod-project';

import { getProject } from './selectors';

import { LINK_ERRORS } from '../editor/constants';

// :: NodeId -> PinKey -> RenderableNodes -> RenderablePin
export const getRenderablePin = R.uncurryN(3, (nodeId, pinKey) => R.path([nodeId, 'pins', pinKey]));

// :: NodeId -> Link -> Boolean
export const isLinkConnectedToNode =
  R.uncurryN(2, nodeId =>
    R.compose(
      R.contains(nodeId),
      getLinkNodeIds
    )
  );

//
// pins linking validation utils
//

// :: RenderablePin -> String | Null
export const getPinSelectionError = (pin) => {
  if (isInputPin(pin) && pin.isConnected) {
    return LINK_ERRORS.ONE_LINK_FOR_INPUT_PIN;
  }

  return null;
};

// :: RenderablePin -> Boolean
export const isPinLinkable = R.complement(getPinSelectionError);

// :: RenderablePin -> RenderablePin -> String | Null
export const getLinkingError = R.curry((pin1, pin2) => {
  if (!isPinLinkable(pin1) || !isPinLinkable(pin2)) {
    return LINK_ERRORS.ONE_LINK_FOR_INPUT_PIN;
  }

  // same direction?
  if (pin1.direction === pin2.direction) return LINK_ERRORS.SAME_DIRECTION;

  // very primitive case of a feedback loop
  // TODO: this needs to be much more complicated (and implemented in xod-project)
  if (pin1.nodeId === pin2.nodeId) {
    return LINK_ERRORS.SAME_NODE;
  }

  const inputPin = isInputPin(pin1) ? pin1 : pin2;
  const outputPin = isOutputPin(pin1) ? pin1 : pin2;

  if (!canCastTypes(outputPin.type, inputPin.type)) {
    return LINK_ERRORS.INCOMPATIBLE_TYPES;
  }

  return null;
});

// :: RenderablePin -> RenderablePin -> Boolean
export const canPinsBeLinked = R.complement(getLinkingError);

export const getJSONForExport = (project) => {
  const libPaths = R.compose(
    R.map(getPatchPath),
    listLibraryPatches
  )(project);

  return R.compose(
    p => JSON.stringify(p, null, 2),
    omitPatches(libPaths)
  )(project);
};

// :: State -> PatchPath -> Boolean
export const isPatchPathTaken = (state, newPatchPath) => {
  const maybeExistingPatch = R.compose(
    getPatchByPath(newPatchPath),
    getProject
  )(state);

  return Maybe.isJust(maybeExistingPatch);
};
<|endoftext|>


<commit_before>var skts_prefService = Components.classes["@mozilla.org/preferences-service;1"]
                                 .getService(Components.interfaces.nsIPrefService)
                                 .getBranch("extensions.skts.");

var skts_prevTabKey = skts_prefService.getCharPref("prevTabKey");
var skts_nextTabKey = skts_prefService.getCharPref("nextTabKey");

/**
 * Returns true if focus is on an element which takes some sort of input. in
 * that case, we do not want to catch key presses.
 */
function skts_inputFocus() {
    var skts_element = document.commandDispatcher.focusedElement;

    // check if focused element takes input
    if (skts_element) {
        var skts_localName = skts_element.localName.toLowerCase();
        if (skts_localName === "input"
        ||  skts_localName === "textarea"
        ||  skts_localName === "select"
        ||  skts_localName === "button"
        ||  skts_localName === "isindex") {
            return true;
        }
    }

    // check if focused element has designMode="on"
    var skts_window = document.commandDispatcher.focusedWindow;
    if (skts_window) {
        if(skts_window.document.designMode === "on") {
            return true;
        }
    }

    // if we got this far, we should be able to catch key presses without
    // messing up something else; return false
    return false;
}

/**
 * Stop an Event from being propagated further. Called when an Event has been
 * handled.
 */
function skts_stopEvent(evt) {
    evt.preventDefault();
    evt.stopPropagation();
}

/**
 * Handles KeyboardEvents.
 */
function skts_switchTab(evt) {

    // if an input element has focus, we want to let the user input text into
    // the element, thus we return before the event is handled.
    if (skts_inputFocus()) {
        return;
    }

    // we only want to handle single key presses. to avoid interfering with
    // other key bindings, we return without handling the event if either of
    // Shift, Ctrl, Alt or Meta is pressed.
    if (evt.shiftKey
    ||  evt.ctrlKey
    ||  evt.altKey
    ||  evt.metaKey) {
        return;
    }

    skts_keyPressed = evt.which

    if (skts_keyPressed === skts_prevTabKey.charCodeAt(0)) {
        gBrowser.mTabContainer.advanceSelectedTab(-1, true);
        skts_stopEvent(evt);
    }
    else if (skts_keyPressed === skts_nextTabKey.charCodeAt(0)) {
        gBrowser.mTabContainer.advanceSelectedTab(1, true);
        skts_stopEvent(evt);
    }
}

window.addEventListener("keypress", skts_switchTab, false);
<commit_msg>encapsulate everything in skts.js in an object<commit_after>var skts = {
  prevTabKey: null,
  nextTabKey: null,

  /**
   * Gets preferences from the Mozilla preference system
   */
  getPreferences: function() {
    var prefService = Components.classes["@mozilla.org/preferences-service;1"]
                                     .getService(Components.interfaces.nsIPrefService)
                                     .getBranch("extensions.skts.");

    this.prevTabKey = prefService.getCharPref("prevTabKey");
    this.nextTabKey = prefService.getCharPref("nextTabKey");
  },

  /**
   * Returns true if focus is on an element which takes some sort of input. in
   * that case, we do not want to catch key presses.
   */
  inputFocus: function() {
    var focusedEl = document.commandDispatcher.focusedElement;

    // check if focused element takes input
    if (focusedEl) {
      var focusedElLn = focusedEl.localName.toLowerCase();
      if (focusedElLn === "input"
      ||  focusedElLn === "textarea"
      ||  focusedElLn === "select"
      ||  focusedElLn === "button"
      ||  focusedElLn === "isindex") {
        return true;
      }
    }

    // check if focused element has designMode="on"
    var focusedWin = document.commandDispatcher.focusedWindow;
    if (focusedWin) {
      if(focusedWin.document.designMode === "on") {
        return true;
      }
    }

    // if we got this far, we should be able to catch key presses without
    // messing up something else; return false
    return false;
  },

  /**
   * Stop an Event from being propagated further. Called when an Event has been
   * handled.
   */
  stopEvent: function(evt) {
    evt.preventDefault();
    evt.stopPropagation();
  },

  /**
   * Handles KeyboardEvents.
   */
  onKeyPress: function(evt) {

    // if an input element has focus, we want to let the user input text into
    // the element, thus we return before the event is handled.
    if (skts.inputFocus()) {
      return;
    }

    // we only want to handle single key presses. to avoid interfering with
    // other key bindings, we return without handling the event if either of
    // Shift, Ctrl, Alt or Meta is pressed.
    if (evt.shiftKey
    ||  evt.ctrlKey
    ||  evt.altKey
    ||  evt.metaKey) {
      return;
    }

    var key = evt.which
    if (key === skts.prevTabKey.charCodeAt(0)) {
      gBrowser.mTabContainer.advanceSelectedTab(-1, true);
      skts.stopEvent(evt);
    }
    else if (key === skts.nextTabKey.charCodeAt(0)) {
      gBrowser.mTabContainer.advanceSelectedTab(1, true);
      skts.stopEvent(evt);
    }
  },

  startup: function() {
    this.getPreferences();
    window.addEventListener("keypress", this.onKeyPress, false);
  }
};

window.addEventListener("load", function(e) { skts.startup(); }, false);

// vim ts=2 sts=2 sw=2 tw=79
<|endoftext|>


<commit_before>import parseProps from '../helpers/parseProps';
import parseFunctional from '../helpers/parseFunctional';
import { parseVariables } from '../features/variables';
import { logSubscription } from '../helpers/logger';

import { runMixin, getMixin } from '../features/mixins';
import { parseKeyValue, parseRawCss, parseRawInline } from '../features/plugins';

import testCondition from '../utils/testCondition';
import printToCss from '../utils/printToCss';

export default class StyleParser {
  constructor(subscription) {
    this.sub = subscription;
  }

  run() {
    const startTime = new Date();
    let rawCss = this.sub.printedCss;
    const { options } = this.sub;
    if(options.styles && !options.dontParse) {
      this.printStyleArray = [];
      this.runningQueue = [...options.styles];
      this.nextQueue = [];

      this.sub.inlineStyles = {};

      this.sub.touched = {
        mixins: {},
        variables: {},
      }
      this.runQueue();
      if(this.printStyleArray.length) {
        this.replacePropsAndVarForSelectors(this.printStyleArray);
        rawCss = printToCss(this.printStyleArray);
      }
    }

    // Run post plugins. (parseRawInline, parseRawCss)
    if(options.inline) {
      this.sub.inlineStyles = parseRawInline(this.sub.inlineStyles, this.sub.props);
    } else {
      this.sub.printedCss = parseRawCss(rawCss || '', this.sub.props);
    }

    if(options.debug) {
      // Hack to make sure that render has been called and we know which props was forwarded and which was not.
      setTimeout(() => {
        logSubscription(this.sub, startTime);
      }, 1);
    }

  }
  runQueue() {
    while (this.runningQueue.length) {
      const node = this.runningQueue.shift();
      const { props, touched } = this.sub;
      switch(node.type) {
        case 'mixin': {
          // On mixins, inject on current queue, to keep hierachy
          const mixinValue = runMixin(node, props, touched);
          if(Array.isArray(mixinValue)) {
            this.runningQueue = mixinValue.concat(this.runningQueue);
          }
          break;
        }
        case 'array': {
          // On array, inject on current queue, to keep hierachy
          this.runningQueue = node.value.concat(this.runningQueue);
          break;
        }
        case 'nested': {
          // Only parse the children if condition is met
          if(!node.condition || testCondition(node.condition, props)) {

            if(node.condition) {
              node.value.forEach((n) => {
                const length = n.selectors.length;
                if(n.selectors[length - 1].indexOf(', .sw_') === -1) {
                  n.selectors[length - 1] += `, .sw_${node.condition.key}`;
                }
              })
            }
            this.nextQueue = this.nextQueue.concat(node.value);
          }
          break;
        }
        case 'node': {
          this.handleNode(node);
          break;
        }
      }
    }
    if(this.nextQueue.length) {
      this.runningQueue = this.nextQueue;
      this.nextQueue = [];
      this.runQueue();
    }
  }
  replacePropsAndVarForSelectors(array) {
    const { props, className, touched } = this.sub;
    array.forEach((obj) => {
      obj.selector = parseProps(obj.selector, props);
      obj.selector = parseVariables(obj.selector, touched.variables);
      // TODO: support comma separated stuff.
      obj.selector = className.split(/,\ ?/g).map(s => obj.selector.replace(/&/gi, s)).join(', ');
      if(obj.children.length) {
        this.replacePropsAndVarForSelectors(obj.children);
      }
    });
  }
  findTargetFromSelectors(selectors) {
    // Support iterative structere with children
    let dRes = { children: this.printStyleArray };
    selectors.forEach((selector) => {
      const length = dRes.children.length;
      let index = length - 1;
      if(!length || dRes.children[length - 1].selector !== selector) {
        index = dRes.children.push({
          selector,
          children: [],
          styles: [],
        }) - 1;
      }
      dRes = dRes.children[index];
    })
    return dRes;
  }
  handleNode(node) {
    let key = node.key;
    let value = node.value;

    const { props, touched, options, inlineStyles } = this.sub;

    value = parseFunctional(value, props);
    value = parseProps(value, props);
    value = parseVariables(value, touched.variables);

    const res = parseKeyValue(key, value, props);
    key = res[0];
    value = res[1];

    // value is nothing, but accept 0
    if(typeof value !== 'number' && !value) return;

    // If inline, just override the values.
    if(options.inline) {
      inlineStyles[key] = value;
    } else {
      // Else figure out order and stuff
      const target = this.findTargetFromSelectors(node.selectors);

      const sIndex = target.styles.findIndex(o => o.key === key);
      if(sIndex > -1 && !node.append) {
        target.styles.splice(sIndex, 1);
      }

      target.styles.push({
        key,
        value,
      });

    }
  }
}<commit_msg>RIP: Nextqueue. Now: Order always win<commit_after>import parseProps from '../helpers/parseProps';
import parseFunctional from '../helpers/parseFunctional';
import { parseVariables } from '../features/variables';
import { logSubscription } from '../helpers/logger';

import { runMixin, getMixin } from '../features/mixins';
import { parseKeyValue, parseRawCss, parseRawInline } from '../features/plugins';

import testCondition from '../utils/testCondition';
import printToCss from '../utils/printToCss';

export default class StyleParser {
  constructor(subscription) {
    this.sub = subscription;
  }

  run() {
    const startTime = new Date();
    let rawCss = this.sub.printedCss;
    const { options } = this.sub;
    if(options.styles && !options.dontParse) {
      this.printStyleArray = [];
      this.runningQueue = [...options.styles];

      this.sub.inlineStyles = {};

      this.sub.touched = {
        mixins: {},
        variables: {},
      }
      this.runQueue();
      if(this.printStyleArray.length) {
        this.replacePropsAndVarForSelectors(this.printStyleArray);
        rawCss = printToCss(this.printStyleArray);
      }
    }

    // Run post plugins. (parseRawInline, parseRawCss)
    if(options.inline) {
      this.sub.inlineStyles = parseRawInline(this.sub.inlineStyles, this.sub.props);
    } else {
      this.sub.printedCss = parseRawCss(rawCss || '', this.sub.props);
    }

    if(options.debug) {
      // Hack to make sure that render has been called and we know which props was forwarded and which was not.
      setTimeout(() => {
        logSubscription(this.sub, startTime);
      }, 1);
    }

  }
  runQueue() {
    while (this.runningQueue.length) {
      const node = this.runningQueue.shift();
      const { props, touched } = this.sub;
      switch(node.type) {
        case 'mixin': {
          // On mixins, inject on current queue, to keep hierachy
          const mixinValue = runMixin(node, props, touched);
          if(Array.isArray(mixinValue)) {
            this.runningQueue = mixinValue.concat(this.runningQueue);
          }
          break;
        }
        case 'array': {
          // On array, inject on current queue, to keep hierachy
          this.runningQueue = node.value.concat(this.runningQueue);
          break;
        }
        case 'nested': {
          // Only parse the children if condition is met
          if(!node.condition || testCondition(node.condition, props)) {

            if(node.condition) {
              node.value.forEach((n) => {
                const length = n.selectors.length;
                if(n.selectors[length - 1].indexOf(', .sw_') === -1) {
                  n.selectors[length - 1] += `, .sw_${node.condition.key}`;
                }
              })
            }
            this.runningQueue = node.value.concat(this.runningQueue);
          }
          break;
        }
        case 'node': {
          this.handleNode(node);
          break;
        }
      }
    }
  }
  replacePropsAndVarForSelectors(array) {
    const { props, className, touched } = this.sub;
    array.forEach((obj) => {
      obj.selector = parseProps(obj.selector, props);
      obj.selector = parseVariables(obj.selector, touched.variables);
      // TODO: support comma separated stuff.
      obj.selector = className.split(/,\ ?/g).map(s => obj.selector.replace(/&/gi, s)).join(', ');
      if(obj.children.length) {
        this.replacePropsAndVarForSelectors(obj.children);
      }
    });
  }
  findTargetFromSelectors(selectors) {
    // Support iterative structere with children
    let dRes = { children: this.printStyleArray };
    selectors.forEach((selector) => {
      const length = dRes.children.length;
      let index = length - 1;
      if(!length || dRes.children[length - 1].selector !== selector) {
        index = dRes.children.push({
          selector,
          children: [],
          styles: [],
        }) - 1;
      }
      dRes = dRes.children[index];
    })
    return dRes;
  }
  handleNode(node) {
    let key = node.key;
    let value = node.value;

    const { props, touched, options, inlineStyles } = this.sub;

    value = parseFunctional(value, props);
    value = parseProps(value, props);
    value = parseVariables(value, touched.variables);

    const res = parseKeyValue(key, value, props);
    key = res[0];
    value = res[1];

    // value is nothing, but accept 0
    if(typeof value !== 'number' && !value) return;

    // If inline, just override the values.
    if(options.inline) {
      inlineStyles[key] = value;
    } else {
      // Else figure out order and stuff
      const target = this.findTargetFromSelectors(node.selectors);

      const sIndex = target.styles.findIndex(o => o.key === key);
      if(sIndex > -1 && !node.append) {
        target.styles.splice(sIndex, 1);
      }

      target.styles.push({
        key,
        value,
      });

    }
  }
}<|endoftext|>


<commit_before>const assert = require('assert')
const {
  assertNoWarnings,
  assertNoErrors,
  assertErrorMessage,
  assertWarnsCount,
  assertErrorCount
} = require('./../../common/asserts')
const linter = require('./../../../lib/index')
const { funcWith } = require('./../../common/contract-builder')

describe('Linter - reason-string', () => {
  it('should raise reason string is mandatory for require', () => {
    const code = funcWith(`require(!has(role, account));
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['warn', { maxLength: 5 }] }
    })

    assert.ok(report.warningCount > 0)
    assertWarnsCount(report, 1)
    assertErrorMessage(report, 'Provide an error message for require')
  })

  it('should raise reason string is mandatory for revert', () => {
    const code = funcWith(`revert(!has(role, account));
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['error', { maxLength: 5 }] }
    })

    assert.ok(report.errorCount > 0)
    assertErrorCount(report, 1)
    assertErrorMessage(report, 'Provide an error message for revert')
  })

  it('should raise reason string maxLength error for require', () => {
    const code = funcWith(`require(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['warn', { maxLength: 5 }] }
    })

    assert.ok(report.warningCount > 0)
    assertWarnsCount(report, 1)
    assertErrorMessage(report, 'Error message for require is too long')
  })

  it('should raise reason string maxLength error for revert', () => {
    const code = funcWith(`revert(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['error', { maxLength: 5 }] }
    })

    assert.ok(report.errorCount > 0)
    assertErrorCount(report, 1)
    assertErrorMessage(report, 'Error message for revert is too long')
  })

  it('should not raise warning for require', () => {
    const code = funcWith(`require(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['warn', { maxLength: 50 }] }
    })

    assertNoWarnings(report)
    assertNoErrors(report)
  })

  it('should not raise error for revert', () => {
    const code = funcWith(`revert(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['error', { maxLength: 50 }] }
    })

    assertNoWarnings(report)
    assertNoErrors(report)
  })

  it('should not raise warning for require with text errors', () => {
    const code = funcWith(`require
    (!has(role, account),

    "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['warn', { maxLength: 50 }] }
    })

    assertNoWarnings(report)
    assertNoErrors(report)
  })

  it('should not raise error for revert with text errors', () => {
    const code = funcWith(`     revert
    (!has(role, account),
    "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['error', { maxLength: 50 }] }
    })

    assertNoWarnings(report)
    assertNoErrors(report)
  })
})
<commit_msg>Update max length to 31<commit_after>const assert = require('assert')
const {
  assertNoWarnings,
  assertNoErrors,
  assertErrorMessage,
  assertWarnsCount,
  assertErrorCount
} = require('./../../common/asserts')
const linter = require('./../../../lib/index')
const { funcWith } = require('./../../common/contract-builder')

describe('Linter - reason-string', () => {
  it('should raise reason string is mandatory for require', () => {
    const code = funcWith(`require(!has(role, account));
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['warn', { maxLength: 5 }] }
    })

    assert.ok(report.warningCount > 0)
    assertWarnsCount(report, 1)
    assertErrorMessage(report, 'Provide an error message for require')
  })

  it('should raise reason string is mandatory for revert', () => {
    const code = funcWith(`revert(!has(role, account));
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['error', { maxLength: 5 }] }
    })

    assert.ok(report.errorCount > 0)
    assertErrorCount(report, 1)
    assertErrorMessage(report, 'Provide an error message for revert')
  })

  it('should raise reason string maxLength error for require', () => {
    const code = funcWith(`require(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['warn', { maxLength: 5 }] }
    })

    assert.ok(report.warningCount > 0)
    assertWarnsCount(report, 1)
    assertErrorMessage(report, 'Error message for require is too long')
  })

  it('should raise reason string maxLength error for revert', () => {
    const code = funcWith(`revert(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['error', { maxLength: 5 }] }
    })

    assert.ok(report.errorCount > 0)
    assertErrorCount(report, 1)
    assertErrorMessage(report, 'Error message for revert is too long')
  })

  it('should not raise warning for require', () => {
    const code = funcWith(`require(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['warn', { maxLength: 31 }] }
    })

    assertNoWarnings(report)
    assertNoErrors(report)
  })

  it('should not raise error for revert', () => {
    const code = funcWith(`revert(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['error', { maxLength: 50 }] }
    })

    assertNoWarnings(report)
    assertNoErrors(report)
  })

  it('should not raise warning for require with text errors', () => {
    const code = funcWith(`require
    (!has(role, account),

    "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['warn', { maxLength: 50 }] }
    })

    assertNoWarnings(report)
    assertNoErrors(report)
  })

  it('should not raise error for revert with text errors', () => {
    const code = funcWith(`     revert
    (!has(role, account),
    "Roles: account already has role");
        role.bearer[account] = true;role.bearer[account] = true;
    `)

    const report = linter.processStr(code, {
      rules: { 'reason-string': ['error', { maxLength: 50 }] }
    })

    assertNoWarnings(report)
    assertNoErrors(report)
  })
})
<|endoftext|>


<commit_before>'use strict'

const util = require('util')

const MODIFIER_CASE_INSENSATIVE = '~*'
const MODIFIER_CASE_SENSATIVE = '~'
const MODIFIER_PREFIX = ''
const MODIFIER_EQUAL = '='
const MODIFIER_PREFIX_LONGEST = '^~'

class Location {
  constructor ({
    location,
    modifier = MODIFIER_PREFIX
  }) {
    this._modifier = modifier
    this._location = location

    this._length = modifier === MODIFIER_PREFIX
      || modifier === MODIFIER_PREFIX_LONGEST
      ? this._location.length
      : 0
  }

  // @returns {Object}
  // - last `Boolean`
  // - match `Boolean`
  match (pathname) {
    const matcher = MATCHER_MAP[this._modifier]
    const match = matcher(this._location, pathname)

    return {
      match,
      modifier: this._modifier
    }
  }

  length () {
    return this._length
  }
}

Location.MODIFIERS = {
  MODIFIER_CASE_INSENSATIVE,
  MODIFIER_CASE_SENSATIVE,
  MODIFIER_PREFIX,
  MODIFIER_EQUAL,
  MODIFIER_PREFIX_LONGEST
}

function regex_match (matcher, pathname) {
  return matcher.test(pathname)
}

function prefix_match (matcher, pathname) {
  return pathname.indexOf(matcher) === 0
}

const MATCHER_MAP = {
  [MODIFIER_CASE_INSENSATIVE]: (matcher, pathname) => {
    return matcher === pathname
  },

  [MODIFIER_PREFIX]: prefix_match,
  [MODIFIER_PREFIX_LONGEST]: prefix_match,
  [MODIFIER_CASE_INSENSATIVE]: regex_match,
  [MODIFIER_CASE_SENSATIVE]: regex_match
}

// Sanitize location directive.
// Dirty works
// @param {Object} location, the location object
Location.from = ({location, location_is, modifier}) => {

  function factory () {
    return new Location({
      location,
      modifier
    })
  }

  if (location_is) {
    location = location_is
    modifier = '='

    return factory()
  }

  if (util.isRegExp(location)) {
    modifier = ~location.flags.indexOf('i')
      ? MODIFIER_CASE_INSENSATIVE
      : MODIFIER_CASE_SENSATIVE

    return factory()
  }

  if (typeof location === 'string') {
    modifier = ''

    return factory()
  }

  throw new TypeError('invalid location "${location}"')
}

module.exports = Location
<commit_msg>es6 static<commit_after>'use strict'

const util = require('util')

const MODIFIER_CASE_INSENSATIVE = '~*'
const MODIFIER_CASE_SENSATIVE = '~'
const MODIFIER_PREFIX = ''
const MODIFIER_EQUAL = '='
const MODIFIER_PREFIX_LONGEST = '^~'

const MATCHER_MAP = {
  [MODIFIER_CASE_INSENSATIVE]: (matcher, pathname) => {
    return matcher === pathname
  },

  [MODIFIER_PREFIX]: prefix_match,
  [MODIFIER_PREFIX_LONGEST]: prefix_match,
  [MODIFIER_CASE_INSENSATIVE]: regex_match,
  [MODIFIER_CASE_SENSATIVE]: regex_match
}

module.exports = class Location {
  static MODIFIERS = {
    MODIFIER_CASE_INSENSATIVE,
    MODIFIER_CASE_SENSATIVE,
    MODIFIER_PREFIX,
    MODIFIER_EQUAL,
    MODIFIER_PREFIX_LONGEST
  }

  // Sanitize location directive.
  // Dirty works
  // @param {Object} location, the location object
  static from ({
    location,
    location_is,
    modifier
  }) {

    function factory () {
      return new Location({
        location,
        modifier
      })
    }

    if (location_is) {
      location = location_is
      modifier = '='

      return factory()
    }

    if (util.isRegExp(location)) {
      modifier = ~location.flags.indexOf('i')
        ? MODIFIER_CASE_INSENSATIVE
        : MODIFIER_CASE_SENSATIVE

      return factory()
    }

    if (typeof location === 'string') {
      modifier = ''

      return factory()
    }

    throw new TypeError('invalid location "${location}"')
  }

  constructor ({
    location,
    modifier = MODIFIER_PREFIX
  }) {
    this._modifier = modifier
    this._location = location

    this._length = modifier === MODIFIER_PREFIX
      || modifier === MODIFIER_PREFIX_LONGEST
      ? this._location.length
      : 0
  }

  // @returns {Object}
  // - last `Boolean`
  // - match `Boolean`
  match (pathname) {
    const matcher = MATCHER_MAP[this._modifier]
    const match = matcher(this._location, pathname)

    return {
      match,
      modifier: this._modifier
    }
  }

  length () {
    return this._length
  }
}

function regex_match (matcher, pathname) {
  return matcher.test(pathname)
}

function prefix_match (matcher, pathname) {
  return pathname.indexOf(matcher) === 0
}
<|endoftext|>