<commit_before>'use strict';

/**
 * ButtressJS - Realtime datastore for business software
 *
 * @file appointment.js
 * @description Appointment model definition.
 * @module Model
 * @exports model, schema, constants
 * @author Chris Bates-Keegan
 *
 */

const mongoose = require('mongoose');
const Model = require('../');
const Logging = require('../../logging');
const Shared = require('../shared');

/* ********************************************************************************
 *
 * LOCALS
 *
 **********************************************************************************/
let schema = new mongoose.Schema();
let ModelDef = null;

/* ********************************************************************************
 *
 * EMBEDDED DEPENDENCIES
 *
 **********************************************************************************/
Model.initModel('Contact');

/* ********************************************************************************
 *
 * CONSTANTS
 *
 **********************************************************************************/
let constants = {};

const outcomes = [
  'fail',
  'defer',
  'progressed',
  'success'
];
const Outcomes = {
  FAIL: outcomes[0],
  DEFER: outcomes[1],
  PROGRESSED: outcomes[2],
  SUCCESS: outcomes[3]
};

constants.Outcomes = Outcomes;

/* ********************************************************************************
 *
 * SCHEMA
 *
 **********************************************************************************/
schema.add({
  name: String,
  _app: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'App'
  },
  ownerId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  },
  assignedToUserId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  },
  assignedToAccepted: {
    type: Boolean,
    default: false
  },
  companyId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Company'
  },
  contactId: String,
  locationId: String,
  intelApproval: {
    status: {
      type: String,
      default: 'pending'
    },
    timestamp: {
      type: Date,
      default: Date.create
    },
    approverId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'User'
    }
  },
  calendarEntryId: String,
  date: {
    type: Date
  },
  outcome: {
    type: String,
    enum: outcomes
  },
  reason: {
    type: String
  },
  metadata: [{key: String, value: String}],
  notes: [{
    userId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'User'
    },
    text: String,
    timestamp: {
      type: Date,
      default: Date.create
    }
  }]
});

/* ********************************************************************************
 *
 * VIRTUALS
 *
 **********************************************************************************/
schema.virtual('details').get(function() {
  return {
    id: this._id,
    name: this.name,
    ownerId: this.ownerId && this.ownerId._id ? this.ownerId._id : this.ownerId,
    assignedToUserId: this.assignedToUserId && this.assignedToUserId._id ? this.assignedToUserId._id : this.assignedToUserId,
    assignedToAccepted: this.assignedToAccepted,
    companyId: this.companyId && this.companyId._id ? this.companyId._id : this.companyId,
    contactId: this.contactId,
    calendarEntryId: this.calendarEntryId,
    locationId: this.locationId,
    intelApproval: this.intelApproval,
    date: this.date,
    outcome: this.outcome,
    reason: this.reason,
    notes: this.notes.map(n => ({text: n.text, timestamp: n.timestamp, userId: n.userId}))
  };
});

/* ********************************************************************************
 *
 * STATICS
 *
 **********************************************************************************/
/**
 * @param {Object} body - body passed through from a POST request to be validated
 * @return {Object} - returns an object with validation context
 */
const __doValidation = body => {
  let res = {
    isValid: false,
    missing: [],
    invalid: []
  };

  if (!body.name) {
    res.missing.push('name');
    return res;
  }
  if (!body.ownerId) {
    res.missing.push('ownerId');
    return res;
  }
  if (!body.assignedToUserId) {
    res.missing.push('assignedToUserId');
    return res;
  }
  if (!body.companyId) {
    res.missing.push('companyId');
    return res;
  }
  if (!body.contactId) {
    res.missing.push('contactId');
    return res;
  }
  if (!body.date) {
    res.missing.push('date');
    return res;
  }

  res.isValid = true;
  return res;
};

schema.statics.validate = body => {
  if (body instanceof Array === false) {
    body = [body];
  }
  let validation = body.map(__doValidation).filter(v => v.isValid === false);

  return validation.length >= 1 ? validation[0] : {isValid: true};
};

/*
 * @param {Object} body - body passed through from a POST request
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
const __add = body => {
  return prev => {
    Logging.logDebug(body);
    const cl = new ModelDef({
      _app: Model.authApp._id,
      name: body.name,
      date: Date.create(body.date),
      ownerId: body.ownerId,
      assignedToUserId: body.assignedToUserId,
      companyId: body.companyId,
      contactId: body.contactId,
      locationId: body.locationId,
      notes: body.notes
    });

    return cl.save()
      .then(cl => prev.concat([cl]));
  };
};

schema.statics.add = body => {
  if (body instanceof Array === false) {
    body = [body];
  }

  return body.reduce((promise, item) => {
    return promise
      .then(__add(item))
      .catch(Logging.Promise.logError());
  }, Promise.resolve([]));
};

const collection = Model.mongoDb.collection('appointments');
/**
 * @return {Promise} - resolves to an array of Apps (native Mongoose objects)
 */
schema.statics.getAll = () => {
  Logging.log(`getAll: ${Model.authApp._id}`, Logging.Constants.LogLevel.DEBUG);
  return collection.find({_app: Model.authApp._id});
};

schema.statics.rmAll = () => {
  return ModelDef.remove({});
};

/* ********************************************************************************
 *
 * UPDATE BY PATH
 *
 **********************************************************************************/

const PATH_CONTEXT = {
  '^outcome$': {type: 'scalar', values: Outcomes},
  '^(reason|contactId|locationId|date|calendarEntryId|assignedToUserId|assignedToAccepted)$': {type: 'scalar', values: []},
  '^intelApproval$': {type: 'scalar', values: []},
  '^notes$': {type: 'vector-add', values: []},
  '^notes.([0-9]{1,3}).__remove__$': {type: 'vector-rm', values: ['']},
  '^notes.([0-9]{1,3}).text$': {type: 'scalar', values: []}
};

schema.statics.validateUpdate = Shared.validateUpdate(PATH_CONTEXT);
schema.methods.updateByPath = Shared.updateByPath(PATH_CONTEXT);

/* ********************************************************************************
 *
 * METHODS
 *
 **********************************************************************************/

/**
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
schema.methods.rm = function() {
  return ModelDef.remove({_id: this._id});
};

/* ********************************************************************************
 *
 * METADATA
 *
 **********************************************************************************/

schema.methods.addOrUpdateMetadata = Shared.addOrUpdateMetadata;
schema.methods.findMetadata = Shared.findMetadata;
schema.methods.rmMetadata = Shared.rmMetadata;

/* ********************************************************************************
 *
 * EXPORTS
 *
 **********************************************************************************/
ModelDef = mongoose.model('Appointment', schema);

module.exports.constants = constants;
module.exports.schema = schema;
module.exports.model = ModelDef;
<commit_msg>- REFACTOR: Dropped date being required in appointment validation<commit_after>'use strict';

/**
 * ButtressJS - Realtime datastore for business software
 *
 * @file appointment.js
 * @description Appointment model definition.
 * @module Model
 * @exports model, schema, constants
 * @author Chris Bates-Keegan
 *
 */

const mongoose = require('mongoose');
const Model = require('../');
const Logging = require('../../logging');
const Shared = require('../shared');

/* ********************************************************************************
 *
 * LOCALS
 *
 **********************************************************************************/
let schema = new mongoose.Schema();
let ModelDef = null;

/* ********************************************************************************
 *
 * EMBEDDED DEPENDENCIES
 *
 **********************************************************************************/
Model.initModel('Contact');

/* ********************************************************************************
 *
 * CONSTANTS
 *
 **********************************************************************************/
let constants = {};

const outcomes = [
  'fail',
  'defer',
  'progressed',
  'success'
];
const Outcomes = {
  FAIL: outcomes[0],
  DEFER: outcomes[1],
  PROGRESSED: outcomes[2],
  SUCCESS: outcomes[3]
};

constants.Outcomes = Outcomes;

/* ********************************************************************************
 *
 * SCHEMA
 *
 **********************************************************************************/
schema.add({
  name: String,
  _app: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'App'
  },
  ownerId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  },
  assignedToUserId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  },
  assignedToAccepted: {
    type: Boolean,
    default: false
  },
  companyId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Company'
  },
  contactId: String,
  locationId: String,
  intelApproval: {
    status: {
      type: String,
      default: 'pending'
    },
    timestamp: {
      type: Date,
      default: Date.create
    },
    approverId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'User'
    }
  },
  calendarEntryId: String,
  date: {
    type: Date
  },
  outcome: {
    type: String,
    enum: outcomes
  },
  reason: {
    type: String
  },
  metadata: [{key: String, value: String}],
  notes: [{
    userId: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'User'
    },
    text: String,
    timestamp: {
      type: Date,
      default: Date.create
    }
  }]
});

/* ********************************************************************************
 *
 * VIRTUALS
 *
 **********************************************************************************/
schema.virtual('details').get(function() {
  return {
    id: this._id,
    name: this.name,
    ownerId: this.ownerId && this.ownerId._id ? this.ownerId._id : this.ownerId,
    assignedToUserId: this.assignedToUserId && this.assignedToUserId._id ? this.assignedToUserId._id : this.assignedToUserId,
    assignedToAccepted: this.assignedToAccepted,
    companyId: this.companyId && this.companyId._id ? this.companyId._id : this.companyId,
    contactId: this.contactId,
    calendarEntryId: this.calendarEntryId,
    locationId: this.locationId,
    intelApproval: this.intelApproval,
    date: this.date,
    outcome: this.outcome,
    reason: this.reason,
    notes: this.notes.map(n => ({text: n.text, timestamp: n.timestamp, userId: n.userId}))
  };
});

/* ********************************************************************************
 *
 * STATICS
 *
 **********************************************************************************/
/**
 * @param {Object} body - body passed through from a POST request to be validated
 * @return {Object} - returns an object with validation context
 */
const __doValidation = body => {
  let res = {
    isValid: false,
    missing: [],
    invalid: []
  };

  if (!body.name) {
    res.missing.push('name');
    return res;
  }
  if (!body.ownerId) {
    res.missing.push('ownerId');
    return res;
  }
  if (!body.assignedToUserId) {
    res.missing.push('assignedToUserId');
    return res;
  }
  if (!body.companyId) {
    res.missing.push('companyId');
    return res;
  }
  if (!body.contactId) {
    res.missing.push('contactId');
    return res;
  }
  // if (!body.date) {
  //   res.missing.push('date');
  //   return res;
  // }

  res.isValid = true;
  return res;
};

schema.statics.validate = body => {
  if (body instanceof Array === false) {
    body = [body];
  }
  let validation = body.map(__doValidation).filter(v => v.isValid === false);

  return validation.length >= 1 ? validation[0] : {isValid: true};
};

/*
 * @param {Object} body - body passed through from a POST request
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
const __add = body => {
  return prev => {
    Logging.logDebug(body);
    const cl = new ModelDef({
      _app: Model.authApp._id,
      name: body.name,
      date: Date.create(body.date),
      ownerId: body.ownerId,
      assignedToUserId: body.assignedToUserId,
      companyId: body.companyId,
      contactId: body.contactId,
      locationId: body.locationId,
      notes: body.notes
    });

    return cl.save()
      .then(cl => prev.concat([cl]));
  };
};

schema.statics.add = body => {
  if (body instanceof Array === false) {
    body = [body];
  }

  return body.reduce((promise, item) => {
    return promise
      .then(__add(item))
      .catch(Logging.Promise.logError());
  }, Promise.resolve([]));
};

const collection = Model.mongoDb.collection('appointments');
/**
 * @return {Promise} - resolves to an array of Apps (native Mongoose objects)
 */
schema.statics.getAll = () => {
  Logging.log(`getAll: ${Model.authApp._id}`, Logging.Constants.LogLevel.DEBUG);
  return collection.find({_app: Model.authApp._id});
};

schema.statics.rmAll = () => {
  return ModelDef.remove({});
};

/* ********************************************************************************
 *
 * UPDATE BY PATH
 *
 **********************************************************************************/

const PATH_CONTEXT = {
  '^outcome$': {type: 'scalar', values: Outcomes},
  '^(reason|contactId|locationId|date|calendarEntryId|assignedToUserId|assignedToAccepted)$': {type: 'scalar', values: []},
  '^intelApproval$': {type: 'scalar', values: []},
  '^notes$': {type: 'vector-add', values: []},
  '^notes.([0-9]{1,3}).__remove__$': {type: 'vector-rm', values: ['']},
  '^notes.([0-9]{1,3}).text$': {type: 'scalar', values: []}
};

schema.statics.validateUpdate = Shared.validateUpdate(PATH_CONTEXT);
schema.methods.updateByPath = Shared.updateByPath(PATH_CONTEXT);

/* ********************************************************************************
 *
 * METHODS
 *
 **********************************************************************************/

/**
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
schema.methods.rm = function() {
  return ModelDef.remove({_id: this._id});
};

/* ********************************************************************************
 *
 * METADATA
 *
 **********************************************************************************/

schema.methods.addOrUpdateMetadata = Shared.addOrUpdateMetadata;
schema.methods.findMetadata = Shared.findMetadata;
schema.methods.rmMetadata = Shared.rmMetadata;

/* ********************************************************************************
 *
 * EXPORTS
 *
 **********************************************************************************/
ModelDef = mongoose.model('Appointment', schema);

module.exports.constants = constants;
module.exports.schema = schema;
module.exports.model = ModelDef;
<|endoftext|>


<commit_before>/**
 * The global namespace for Telescope utils.
 * @namespace Telescope.utils
 */
Telescope.utils = {};

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

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

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

/**
 * Convert a string to camelCase and remove spaces.
 * @param {String} str
 */
Telescope.utils.camelCaseify = function(str) {
  return this.dashToCamel(str.replace(' ', '-'));
};

/**
 * 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.
 */
Telescope.utils.trimWords = function(s, numWords) {
  var expString = s.split(/\s+/,numWords);
  if(expString.length >= numWords)
    return expString.join(" ")+"…";
  return s;
};

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

Telescope.utils.getCurrentTemplate = function() {
  var template = Router.current().lookupTemplate();
  // on postsDaily route, template is a function
  if (typeof template === "function") {
    return template();
  } else {
    return template;
  }
};

Telescope.utils.t = function(message) {
  var d = new Date();
  console.log("### "+message+" rendered at "+d.getHours()+":"+d.getMinutes()+":"+d.getSeconds());
};

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

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

Telescope.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 //
//////////////////////////

/**
 * Returns the user defined site URL or Meteor.absoluteUrl
 */
Telescope.utils.getSiteUrl = function () {
  return Settings.get('siteUrl', Meteor.absoluteUrl());
};

/**
 * The global namespace for Telescope utils.
 * @param {String} url - the URL to redirect
 */
Telescope.utils.getOutgoingUrl = function (url) {
  return Telescope.utils.getRouteUrl('out', {}, {query: {url: url}});
};

// This function should only ever really be necessary server side
// Client side using .path() is a better option since it's relative
// and shouldn't care about the siteUrl.
Telescope.utils.getRouteUrl = function (routeName, params, options) {
  options = options || {};
  var route = Router.url(
    routeName,
    params || {},
    options
  );
  return route;
};

Telescope.utils.getSignupUrl = function() {
  return this.getRouteUrl('atSignUp');
};
Telescope.utils.getSigninUrl = function() {
  return this.getRouteUrl('atSignIn');
};

//TODO: fix this
Telescope.utils.getPostCommentUrl = function(postId, commentId) {
  // get link to a comment on a post page
  return this.getRouteUrl('post_page_comment', {
    _id: postId,
    commentId: commentId
  });
};

Telescope.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;
};

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

Telescope.utils.getDomain = function(url) {
  var urlObject = Npm.require('url');
  return urlObject.parse(url).hostname.replace('www.', '');
};

Telescope.utils.invitesEnabled = function() {
  return Settings.get("requireViewInvite") || Settings.get("requirePostInvite");
};

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

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

Telescope.utils.sanitize = function(s) {
  // console.log('// before sanitization:')
  // console.log(s)
  if(Meteor.isServer){
    s = sanitizeHtml(s, {
      allowedTags: [
        '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;
};

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

Telescope.utils.stripMarkdown = function(s) {
  var html_body = marked(s);
  return stripHTML(html_body);
};

// http://stackoverflow.com/questions/2631001/javascript-test-for-existence-of-nested-object-key
Telescope.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;
};

Telescope.log = function (s) {
  if(Settings.get('debug', false))
    console.log(s);
};

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

<commit_msg>make trimWords work with empty string<commit_after>/**
 * The global namespace for Telescope utils.
 * @namespace Telescope.utils
 */
Telescope.utils = {};

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

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

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

/**
 * Convert a string to camelCase and remove spaces.
 * @param {String} str
 */
Telescope.utils.camelCaseify = function(str) {
  return this.dashToCamel(str.replace(' ', '-'));
};

/**
 * 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.
 */
Telescope.utils.trimWords = function(s, numWords) {

  if (!s)
    return s;

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

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

Telescope.utils.getCurrentTemplate = function() {
  var template = Router.current().lookupTemplate();
  // on postsDaily route, template is a function
  if (typeof template === "function") {
    return template();
  } else {
    return template;
  }
};

Telescope.utils.t = function(message) {
  var d = new Date();
  console.log("### "+message+" rendered at "+d.getHours()+":"+d.getMinutes()+":"+d.getSeconds());
};

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

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

Telescope.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 //
//////////////////////////

/**
 * Returns the user defined site URL or Meteor.absoluteUrl
 */
Telescope.utils.getSiteUrl = function () {
  return Settings.get('siteUrl', Meteor.absoluteUrl());
};

/**
 * The global namespace for Telescope utils.
 * @param {String} url - the URL to redirect
 */
Telescope.utils.getOutgoingUrl = function (url) {
  return Telescope.utils.getRouteUrl('out', {}, {query: {url: url}});
};

// This function should only ever really be necessary server side
// Client side using .path() is a better option since it's relative
// and shouldn't care about the siteUrl.
Telescope.utils.getRouteUrl = function (routeName, params, options) {
  options = options || {};
  var route = Router.url(
    routeName,
    params || {},
    options
  );
  return route;
};

Telescope.utils.getSignupUrl = function() {
  return this.getRouteUrl('atSignUp');
};
Telescope.utils.getSigninUrl = function() {
  return this.getRouteUrl('atSignIn');
};

//TODO: fix this
Telescope.utils.getPostCommentUrl = function(postId, commentId) {
  // get link to a comment on a post page
  return this.getRouteUrl('post_page_comment', {
    _id: postId,
    commentId: commentId
  });
};

Telescope.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;
};

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

Telescope.utils.getDomain = function(url) {
  var urlObject = Npm.require('url');
  return urlObject.parse(url).hostname.replace('www.', '');
};

Telescope.utils.invitesEnabled = function() {
  return Settings.get("requireViewInvite") || Settings.get("requirePostInvite");
};

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

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

Telescope.utils.sanitize = function(s) {
  // console.log('// before sanitization:')
  // console.log(s)
  if(Meteor.isServer){
    s = sanitizeHtml(s, {
      allowedTags: [
        '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;
};

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

Telescope.utils.stripMarkdown = function(s) {
  var html_body = marked(s);
  return stripHTML(html_body);
};

// http://stackoverflow.com/questions/2631001/javascript-test-for-existence-of-nested-object-key
Telescope.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;
};

Telescope.log = function (s) {
  if(Settings.get('debug', false))
    console.log(s);
};

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

<|endoftext|>


<commit_before>(function(){
    var app = angular.module("roster", []);

    /* Controller for Rostermenu */
    app.controller("RosterMenuController", function(){
        this.checkoutAddJid = function(event){
            if(event.key == "Enter")
                Roster_ajaxSearchContact(event.target.value);
        };
    });

    /* Controller for Rosterlist */
    app.controller("RosterController", function($scope){
        $scope.contacts = localStorage.getObject('rosterContacts') || [];
        $scope.groups = [];

        /* Dictionaries */
        $scope.lookupgroups = {};
        $scope.lookupjid = {};

        $scope.initContacts = function(list){
            if($scope.contacts.length == 0){
                for(var i = 0; i < list.length; i++){
                    /* New group */
                    if(!(list[i].groupname in $scope.lookupgroups)){
                        el = {
                            'agroup': list[i].groupname,
                            'agroupitems': [],
                            'tombstone': false,
                        };
                        $scope.pushInPlace(el, $scope.contacts, groupnameCompare);

                        /* Create a reference in the localstorage for toggling */
                        localStorage.setItem("rosterGroup_"+list[0].groupname, true);
                    }
                    /* New jid */
                    if(!(list[i].jid in $scope.lookupjid)){
                        el = {
                            'ajid': list[i].jid,
                            'aval': list[i].value,
                            'ajiditems': [],
                            'tombstone': false,
                        };
                        $scope.pushInPlace(el, $scope.lookupgroups[list[i].groupname].agroupitems, jidAvalCompare);
                    }
                    /* New ressource (can't just push the whole set of same jid because there is no set) */
                    if(!$scope.isInJidItems(list[i].jid, list[i].ressource)){
                        $scope.pushInPlace(list[i], $scope.lookupjid[list[i].jid].ajiditems, ressourceCompare);
                    }
                }
            }
            /* Rebound from cache */
            else{
                for(var i = 0; i < $scope.contacts.length; i++){
                    if(!$scope.contacts[i].tombstone){
                        $scope.lookupgroups[$scope.contacts[i].agroup] = $scope.contacts[i];
                        for(var j = 0; j < $scope.contacts[i].agroupitems.length; j++){
                            if(!$scope.contacts[i].agroupitems[j].tombstone)
                                $scope.lookupjid[$scope.contacts[i].agroupitems[j].ajid] = $scope.contacts[i].agroupitems[j];
                        }
                    }
                }
            }

            $scope.$apply();
        };

        /* $scope.isInGroupItems is only for debugging purpose */
        $scope.isInGroupItems = function(group, jid){
            l = $scope.lookupgroups[group].agroupitems.length;
            for(var i = 0; i < l; i++){
                if($scope.lookupgroups[group].agroupitems[i].ajid == jid)
                    return i;
            }
            return false;
        };

        $scope.isInJidItems = function(jid, ressource){
            l = $scope.lookupjid[jid].ajiditems.length;
            for(var i = 0; i < l; i++){
                if($scope.lookupjid[jid].ajiditems[i].ressource == ressource)
                    return true;
            }
            return false;
        };

        $scope.initGroups = function(list){
            for (var i in list){
                if(localStorage.getItem("rosterGroup_"+i) == null){
                    list[i] = true;
                    localStorage.setItem("rosterGroup_"+i, true);
                }
                else list[i] = localStorage.getItem("rosterGroup_"+i);
            }
            $scope.groups = list;

            $scope.$apply();
        };

        $scope.deleteContact = function(jid){
            $scope.lookupjid[jid].tombstone = true;

            $scope.$apply();
        };

        $scope.pushInPlace = function(element, array, comparer){
            if(array === $scope.contacts){
                dico = $scope.lookupgroups;
                key = "agroup";
            } else {
                dico = $scope.lookupjid;
                key = "ajid";
            }

            /* Put element in the right place inside array */
            index = locationOf(element, array, comparer);
            //if(comparer == groupnameCompare){
            array.splice(index, 0, element);

            /* Update dictionnary from the appropriate index */
            for(var i=index; i<array.length; i++){
                dico[array[i][key]] = array[i];
                //if(comparer == groupnameCompare){
            }
        };

        $scope.updateContact = function(list){
            console.log(list);
            if($scope.contacts === null) $scope.contacts = [];
            /* Group change */
            if((list[0].jid in $scope.lookupjid)
                && !($scope.lookupjid[list[0].jid].ajiditems[0].groupname == list[0].groupname)){
                /* Kill jid from old location or whole group if it's the only jid */
                oldgroupname = $scope.lookupjid[list[0].jid].ajiditems[0].groupname;
                if($scope.lookupgroups[oldgroupname].agroupitems.length == 1)
                    $scope.lookupgroups[oldgroupname].tombstone = true;
                else
                    $scope.lookupjid[list[0].jid].tombstone = true;

                console.log("Groupchange: "+oldgroupname+"["+$scope.lookupgroups[oldgroupname].agroupitems.length+"] => "+list[0].groupname);
            }
            /* New group is not in the list */
            if(!(list[0].groupname in $scope.lookupgroups)) {
                console.log("Creation of new group: "+list[0].groupname);
                /* Create group */
                el = {
                    'agroup': list[0].groupname,
                    'agroupitems': [],
                    'tombstone': false,
                };
                $scope.pushInPlace(el, $scope.contacts, groupnameCompare);
                /* Reference in the localstorage for toggling */
                localStorage.setItem("rosterGroup_"+list[0].groupname, true);
            }

            /* Jid is in the list and no group change */
            if(list[0].jid in $scope.lookupjid
                && ($scope.lookupjid[list[0].jid].ajiditems[0].groupname == list[0].groupname)){
                console.log("Change value of "+list[0].jid+" in "+list[0].groupname+" group.");
                //$scope.lookupgroups[list[0].groupname].agroupitems[gi].ajiditems = list
                //var gi = $scope.isInGroupItems(list[0].groupname, list[0].jid);
                $scope.lookupjid[list[0].jid].aval = list[0].value;
                $scope.lookupjid[list[0].jid].ajiditems = list;
                $scope.lookupgroups[list[0].groupname].agroupitems.sort(jidAvalCompare);
            }
            else{
                console.log("Push "+list[0].jid+"("+list[0].value+") in "+list[0].groupname+" group.");
                el = {
                    'ajid':     list[0].jid,
                    'aval':     list[0].value,
                    'ajiditems': list,
                    'tombstone': false,
                };
                $scope.pushInPlace(el, $scope.lookupgroups[list[0].groupname].agroupitems, jidAvalCompare);
            }
            $scope.$apply();
        };

        this.showHideGroup = function(g){
            ls = localStorage.getItem("rosterGroup_"+g);
            if(ls == null){
                ls = localStorage.getItem("rosterGroup_Ungrouped");
                g = "Ungrouped";
            }

            ls = (ls == 'true' || ls == true) ? 'false' : 'true';

            localStorage.setItem("rosterGroup_"+g, ls);
            $scope.groups[g] = ls;
        };

        this.postChatAction = function(c){
            eval(c.rosterview.openchat);
        };

        this.postJingleAction = function(c){
            Popup.close();
            Popup.open(c.jid + "/" + c.ressource);
        };

        this.groupIsShown = function(grp){
            if(typeof $scope.groups[grp] != "undefined"){
                return $scope.groups[grp];
            }
            else return $scope.groups["Ungrouped"];
        };

        this.offlineIsShown = function(){
            if(localStorage.getItem("rosterShow_offline") == "true")
                return "offlineshown";
            else
                return "";
        };

        this.getContactTitle = function(c){
            status = c.status || "";
            ressource = c.ressource || "";
            title = c.jid;
            if(status != "") title += " - " + status;
            if(ressource != "") title += " - " + ressource;
            return title;
        };

        this.getContactClient = function(c){
            liclass = "";
            if(c.rosterview.client)
                liclass = "client " + c.rosterview.client;
            return liclass;
        };

        this.getJidStatusRessource = function(c){
            lititle = c.jid;
            if(c.status != '') lititle += " - " + c.status;
            lititle += " - " + c.ressource;
            return lititle;
        };

        this.getPresenceInactiveClient = function(c){
            liclass = c.rosterview.presencetxt + " " + contact.rosterview.inactive;
            if(c.client) liclass += " client " + c.client;
            return liclass;
        };
    });
})();

window.onunload = window.onbeforeunload = function(e){
    localStorage.setObject('rosterContacts', angular.element(roster).scope().contacts);
};

/* Functions to call angular inner functions */
function initContacts(tab){
    if(tab.length == 0)
        angular.element(roster).scope().contacts = null;
    else /*if(localStorage.getObject("rosterContacts") === null)*/{
        angular.element(roster).scope().initContacts(JSON.parse(tab));
    }
}

function initGroups(tab){
    angular.element(roster).scope().initGroups(JSON.parse(tab));
}

function updateContact(tab){
    angular.element(roster).scope().updateContact(JSON.parse(tab));
}

function deleteContact(jid){
    angular.element(roster).scope().deleteContact(jid);
}

function locationOf(element, array, comparer, start, end) {
    if (array.length === 0)
        return 0;
    start = start || 0;
    end = end || array.length;
    var pivot = (start + end) >> 1;  // >>1 = /2
    var c = comparer(element, array[pivot]);
    //if(comparer == groupnameCompare){
    if ((end - start) <= 1){
        return (c == -1) ? pivot : pivot+1;
    }

    switch (c) {
        case -1: return locationOf(element, array, comparer, start, pivot);
        case 0: return pivot;
        case 1: return locationOf(element, array, comparer, pivot, end);
    };
};

/* Object comparison functions */
var groupnameCompare = function(a, b) {
    //console.log(a.agroup+" vs "+b.agroup);
    return a.agroup.localeCompare(b.agroup);
};

var ressourceCompare = function(a, b) {
    n = a.value - b.value;
    //console.log(a.value+" vs "+b.value);
    return n ? n < 0 ? -1 : 1 : 0;
};

var jidCompare = function(a, b) {
    //console.log(a.jid+" vs "+b.ajid);
    return a.jid.localeCompare(b.ajid);
};

var jidAvalCompare = function(a, b) {
    n = a.aval - b.aval;
    return n ? n < 0 ? -1 : 1 : 0;
};

/* === Old functions still in use === */
function showHideOffline() {
    if(localStorage.getItem("rosterShow_offline") != "true" ){
        document.querySelector('ul#rosterlist').className = 'offlineshown';
        localStorage.setItem("rosterShow_offline", "true");
    }
    else{
        document.querySelector('ul#rosterlist').className = '';
        localStorage.setItem("rosterShow_offline", "false");
    }
}

MovimWebsocket.attach(function()
{
    Roster_ajaxGetRoster();
});

movim_add_onload(function()
{
    var search      = document.querySelector('#rostersearch');
    var roster      = document.querySelector('#roster');
    var rosterlist  = document.querySelector('#rosterlist');

    var roster_classback      = document.querySelector('#roster').className;
    var rosterlist_classback  = document.querySelector('#rosterlist').className;

    roster.onblur  = function() {
        roster.className = roster_classback;
        rosterlist.className = rosterlist_classback;
    };
    search.onkeyup = function(event) {
        if(search.value.length > 0) {
            roster.className = 'search';
            rosterlist.className = 'offlineshown';
        } else {
            roster.className = roster_classback;
            rosterlist.className = rosterlist_classback;
        }

        // We clear the old search
        var selector_clear = '#rosterlist div > li';
        var li = document.querySelectorAll(selector_clear);

        for(i = 0; i < li.length; i++) {
            li.item(i).className = '';
        }

        // We select the interesting li
        var selector = '#rosterlist div > li[title*=\'' + search.value + '\']';
        var li = document.querySelectorAll(selector);

        for(i = 0; i < li.length; i++) {
            li.item(i).className = 'found';
        }
    };
});
<commit_msg>- Merge with nodpounod<commit_after>(function(){
    var app = angular.module("roster", []);

    /* Controller for Rostermenu */
    app.controller("RosterMenuController", function(){
        this.checkoutAddJid = function(event){
            if(event.key == "Enter")
                Roster_ajaxSearchContact(event.target.value);
        };
    });

    /* Controller for Rosterlist */
    app.controller("RosterController", function($scope){
        $scope.contacts = localStorage.getObject('rosterContacts') || [];
        $scope.groups = [];

        /* Dictionaries */
        $scope.lookupgroups = {};
        $scope.lookupjid = {};

        $scope.initContacts = function(list){
            if($scope.contacts.length == 0){
                for(var i = 0; i < list.length; i++){
                    /* New group */
                    if(!(list[i].groupname in $scope.lookupgroups)){
                        el = {
                            'agroup': list[i].groupname,
                            'agroupitems': [],
                            'tombstone': false,
                        };
                        $scope.pushInPlace(el, $scope.contacts, groupnameCompare);

                        /* Create a reference in the localstorage for toggling */
                        localStorage.setItem("rosterGroup_"+list[0].groupname, true);
                    }
                    /* New jid */
                    if(!(list[i].jid in $scope.lookupjid)){
                        el = {
                            'ajid': list[i].jid,
                            'aval': list[i].value,
                            'ajiditems': [],
                            'tombstone': false,
                        };
                        $scope.pushInPlace(el, $scope.lookupgroups[list[i].groupname].agroupitems, jidAvalCompare);
                    }
                    /* New ressource (can't just push the whole set of same jid because there is no set) */
                    if(!$scope.isInJidItems(list[i].jid, list[i].ressource)){
                        $scope.pushInPlace(list[i], $scope.lookupjid[list[i].jid].ajiditems, ressourceCompare);
                    }
                }
            }
            /* Rebound from cache */
            else{
                for(var i = 0; i < $scope.contacts.length; i++){
                    if(!$scope.contacts[i].tombstone){
                        $scope.lookupgroups[$scope.contacts[i].agroup] = $scope.contacts[i];
                        for(var j = 0; j < $scope.contacts[i].agroupitems.length; j++){
                            if(!$scope.contacts[i].agroupitems[j].tombstone)
                                $scope.lookupjid[$scope.contacts[i].agroupitems[j].ajid] = $scope.contacts[i].agroupitems[j];
                            else
                                $scope.contacts[i].agroupitems.splice(j, 1);                        }
                    }
                    else
                        $scope.contacts.splice(i, 1);
                }
            }

            $scope.$apply();
        };

        /* $scope.isInGroupItems is only for debugging purpose */
        $scope.isInGroupItems = function(group, jid){
            l = $scope.lookupgroups[group].agroupitems.length;
            for(var i = 0; i < l; i++){
                if($scope.lookupgroups[group].agroupitems[i].ajid == jid)
                    return i;
            }
            return false;
        };

        $scope.isInJidItems = function(jid, ressource){
            l = $scope.lookupjid[jid].ajiditems.length;
            for(var i = 0; i < l; i++){
                if($scope.lookupjid[jid].ajiditems[i].ressource == ressource)
                    return true;
            }
            return false;
        };

        $scope.initGroups = function(list){
            for (var i in list){
                if(localStorage.getItem("rosterGroup_"+i) == null){
                    list[i] = true;
                    localStorage.setItem("rosterGroup_"+i, true);
                }
                else list[i] = localStorage.getItem("rosterGroup_"+i);
            }
            $scope.groups = list;

            $scope.$apply();
        };

        $scope.deleteContact = function(jid){
            $scope.lookupjid[jid].tombstone = true;

            $scope.$apply();
        };

        $scope.pushInPlace = function(element, array, comparer){
            if(array === $scope.contacts){
                dico = $scope.lookupgroups;
                key = "agroup";
            } else {
                dico = $scope.lookupjid;
                key = "ajid";
            }

            /* Put element in the right place inside array */
            index = locationOf(element, array, comparer);
            //if(comparer == groupnameCompare){
            array.splice(index, 0, element);

            /* Update dictionnary from the appropriate index */
            for(var i=index; i<array.length; i++){
                dico[array[i][key]] = array[i];
                //if(comparer == groupnameCompare){
            }
        };

        $scope.updateContact = function(list){
            console.log(list);
            if($scope.contacts === null) $scope.contacts = [];
            /* Group change */
            if((list[0].jid in $scope.lookupjid)
                && !($scope.lookupjid[list[0].jid].ajiditems[0].groupname == list[0].groupname)){
                /* Kill jid from old location or whole group if it's the only jid */
                oldgroupname = $scope.lookupjid[list[0].jid].ajiditems[0].groupname;
                if($scope.lookupgroups[oldgroupname].agroupitems.length == 1)
                    $scope.lookupgroups[oldgroupname].tombstone = true;
                else
                    $scope.lookupjid[list[0].jid].tombstone = true;

                console.log("Removed jid from oldgroup : "+oldgroupname+"["+$scope.lookupgroups[oldgroupname].agroupitems.length+"] => "+list[0].groupname);
            }
            /* New group is not in the list */
            if(!(list[0].groupname in $scope.lookupgroups)) {
                console.log("Creation of new group: "+list[0].groupname);
                /* Create group */
                el = {
                    'agroup': list[0].groupname,
                    'agroupitems': [],
                    'tombstone': false,
                };
                $scope.pushInPlace(el, $scope.contacts, groupnameCompare);
                /* Reference in the localstorage for toggling */
                localStorage.setItem("rosterGroup_"+list[0].groupname, true);
            }
            /* New Group has been killed before */
            /*else if($scope.lookupgroups[list[0].groupname].tombstone) {
                console.log("Tombstone is true for "+list[0].groupname);
                $scope.lookupgroups[list[0].groupname].tombstone = false;
                $scope.lookupgroups[list[0].groupname].agroupitems = [];
            }*/

            /* Jid is in the list and no group change */
            if(list[0].jid in $scope.lookupjid
                && ($scope.lookupjid[list[0].jid].ajiditems[0].groupname == list[0].groupname)){
                console.log("Change value of "+list[0].jid+" in "+list[0].groupname+" group.");
                //$scope.lookupgroups[list[0].groupname].agroupitems[gi].ajiditems = list
                //var gi = $scope.isInGroupItems(list[0].groupname, list[0].jid);
                $scope.lookupjid[list[0].jid].aval = list[0].value;
                $scope.lookupjid[list[0].jid].ajiditems = list;
                $scope.lookupgroups[list[0].groupname].agroupitems.sort(jidAvalCompare);
            }
            else{
                console.log("Push "+list[0].jid+"("+list[0].value+") in "+list[0].groupname+" group.");
                el = {
                    'ajid':     list[0].jid,
                    'aval':     list[0].value,
                    'ajiditems': list,
                    'tombstone': false,
                };
                $scope.pushInPlace(el, $scope.lookupgroups[list[0].groupname].agroupitems, jidAvalCompare);
            }
            $scope.$apply();
        };

        this.showHideGroup = function(g){
            ls = localStorage.getItem("rosterGroup_"+g);
            if(ls == null){
                ls = localStorage.getItem("rosterGroup_Ungrouped");
                g = "Ungrouped";
            }

            ls = (ls == 'true' || ls == true) ? 'false' : 'true';

            localStorage.setItem("rosterGroup_"+g, ls);
            $scope.groups[g] = ls;
        };

        this.postChatAction = function(c){
            eval(c.rosterview.openchat);
        };

        this.postJingleAction = function(c){
            Popup.close();
            Popup.open(c.jid + "/" + c.ressource);
        };

        this.groupIsShown = function(grp){
            if(typeof $scope.groups[grp] != "undefined"){
                return $scope.groups[grp];
            }
            else return $scope.groups["Ungrouped"];
        };

        this.offlineIsShown = function(){
            if(localStorage.getItem("rosterShow_offline") == "true")
                return "offlineshown";
            else
                return "";
        };

        this.getContactTitle = function(c){
            status = c.status || "";
            ressource = c.ressource || "";
            title = c.jid;
            if(status != "") title += " - " + status;
            if(ressource != "") title += " - " + ressource;
            return title;
        };

        this.getContactClient = function(c){
            liclass = "";
            if(c.rosterview.client)
                liclass = "client " + c.rosterview.client;
            return liclass;
        };

        this.getJidStatusRessource = function(c){
            lititle = c.jid;
            if(c.status != '') lititle += " - " + c.status;
            lititle += " - " + c.ressource;
            return lititle;
        };

        this.getPresenceInactiveClient = function(c){
            liclass = c.rosterview.presencetxt + " " + contact.rosterview.inactive;
            if(c.client) liclass += " client " + c.client;
            return liclass;
        };
    });
})();

window.onunload = window.onbeforeunload = function(e){
    localStorage.setObject('rosterContacts', angular.element(roster).scope().contacts);
};

/* Functions to call angular inner functions */
function initContacts(tab){
    if(tab.length == 0)
        angular.element(roster).scope().contacts = null;
    else /*if(localStorage.getObject("rosterContacts") === null)*/{
        angular.element(roster).scope().initContacts(JSON.parse(tab));
    }
}

function initGroups(tab){
    angular.element(roster).scope().initGroups(JSON.parse(tab));
}

function updateContact(tab){
    angular.element(roster).scope().updateContact(JSON.parse(tab));
}

function deleteContact(jid){
    angular.element(roster).scope().deleteContact(jid);
}

function locationOf(element, array, comparer, start, end) {
    if (array.length === 0)
        return 0;
    start = start || 0;
    end = end || array.length;
    var pivot = (start + end) >> 1;  // >>1 = /2
    var c = comparer(element, array[pivot]);
    //if(comparer == groupnameCompare){
    if ((end - start) <= 1){
        return (c == -1) ? pivot : pivot+1;
    }

    switch (c) {
        case -1: return locationOf(element, array, comparer, start, pivot);
        case 0: return pivot;
        case 1: return locationOf(element, array, comparer, pivot, end);
    };
};

/* Object comparison functions */
var groupnameCompare = function(a, b) {
    //console.log(a.agroup+" vs "+b.agroup);
    return a.agroup.localeCompare(b.agroup);
};

var ressourceCompare = function(a, b) {
    n = a.value - b.value;
    //console.log(a.value+" vs "+b.value);
    return n ? n < 0 ? -1 : 1 : 0;
};

var jidCompare = function(a, b) {
    //console.log(a.jid+" vs "+b.ajid);
    return a.jid.localeCompare(b.ajid);
};

var jidAvalCompare = function(a, b) {
    n = a.aval - b.aval;
    return n ? n < 0 ? -1 : 1 : 0;
};

/* === Old functions still in use === */
function showHideOffline() {
    if(localStorage.getItem("rosterShow_offline") != "true" ){
        document.querySelector('ul#rosterlist').className = 'offlineshown';
        localStorage.setItem("rosterShow_offline", "true");
    }
    else{
        document.querySelector('ul#rosterlist').className = '';
        localStorage.setItem("rosterShow_offline", "false");
    }
}

MovimWebsocket.attach(function()
{
    Roster_ajaxGetRoster();
});

movim_add_onload(function()
{
    var search      = document.querySelector('#rostersearch');
    var roster      = document.querySelector('#roster');
    var rosterlist  = document.querySelector('#rosterlist');

    var roster_classback      = document.querySelector('#roster').className;
    var rosterlist_classback  = document.querySelector('#rosterlist').className;

    roster.onblur  = function() {
        roster.className = roster_classback;
        rosterlist.className = rosterlist_classback;
    };
    search.onkeyup = function(event) {
        if(search.value.length > 0) {
            roster.className = 'search';
            rosterlist.className = 'offlineshown';
        } else {
            roster.className = roster_classback;
            rosterlist.className = rosterlist_classback;
        }

        // We clear the old search
        var selector_clear = '#rosterlist div > li';
        var li = document.querySelectorAll(selector_clear);

        for(i = 0; i < li.length; i++) {
            li.item(i).className = '';
        }

        // We select the interesting li
        var selector = '#rosterlist div > li[title*=\'' + search.value + '\']';
        var li = document.querySelectorAll(selector);

        for(i = 0; i < li.length; i++) {
            li.item(i).className = 'found';
        }
    };
});
<|endoftext|>


<commit_before>var Upload = {
    xhr : null,
    attached : new Array(),
    get : null,
    name : null,
    file : null,

    init : function() {
        document.getElementById('file').addEventListener('change', function(){
            var file = this.files[0];

            Upload.name = file.name;

            Upload.preview(file);
        });
    },

    attach : function(func) {
        if(typeof(func) === "function") {
            this.attached.push(func);
        }
    },

    launchAttached : function() {
        for(var i = 0; i < Upload.attached.length; i++) {
            Upload.attached[i]();
        }
    },

    preview : function(file) {
        if (!file.type.match(/image.*/)) {
            console.log("Not a picture !");
            Upload.initiate(file);
        } else {
            var reader = new FileReader();
            reader.readAsDataURL(file);

            reader.onload = function (ev) {
                Upload.compress(ev.target.result, file);
            };
        };
    },

    compress : function(src, file) {
        var image = new Image();
        image.onload = function()
        {
            var limit = 1920;

            var ratio = Math.min(limit / image.naturalWidth, limit / image.naturalHeight);

            var width = image.naturalWidth;
            var height = image.naturalHeight;

            if(ratio < 1 || file.size > 320000) {
                if(ratio < 1) {
                    width = Math.round(width*ratio);
                    height = Math.round(height*ratio);
                }

                var canvas = document.createElement('canvas');
                canvas.width = width;
                canvas.height = height;

                canvas.getContext("2d").drawImage(image, 0, 0, width, height);

                if(typeof canvas.toBlob == 'function') {
                    canvas.toBlob(
                        function (blob) {
                            Upload.initiate(blob);
                        },
                        'image/jpeg',
                        0.85
                    );
                } else {
                    Upload.initiate(file);
                }
            } else {
                Upload.initiate(file);
            }
        }
        image.src = src;
    },

    initiate : function(file) {
        Upload.file = file;

        Upload_ajaxSend({
            name: Upload.name,
            size: file.size,
            type: file.type
        });
    },

    request : function(get, put) {
        Upload.get = get;

        Upload.xhr = new XMLHttpRequest();

        Upload.xhr.upload.addEventListener('progress', function(evt) {
            var percent = Math.floor(evt.loaded/evt.total*100);
            var progress = document.querySelector('#dialog ul li p');
            if(progress) progress.innerHTML = percent + '%';
        }, false);

        Upload.xhr.onreadystatechange = function() {
            if(Upload.xhr.readyState == 4
            && Upload.xhr.status == 200) {
                Dialog.clear();
                Upload.launchAttached();
            }

            if(Upload.xhr.readyState == 1
            && Upload.xhr.status == 0) {
                Upload_ajaxFailed();
            }
        }

        Upload.xhr.open("PUT", put, true);

        Upload.xhr.setRequestHeader('Content-Type', 'text/plain');
        Upload.xhr.send(Upload.file);
    }
}

MovimWebsocket.attach(function()
{
});
<commit_msg>Handle properly the errors and add .jpg after the file names (if != jpg)<commit_after>var Upload = {
    xhr : null,
    attached : new Array(),
    get : null,
    name : null,
    file : null,

    init : function() {
        document.getElementById('file').addEventListener('change', function(){
            var file = this.files[0];

            Upload.name = file.name;

            Upload.preview(file);
        });
    },

    attach : function(func) {
        if(typeof(func) === "function") {
            this.attached.push(func);
        }
    },

    launchAttached : function() {
        for(var i = 0; i < Upload.attached.length; i++) {
            Upload.attached[i]();
        }
    },

    preview : function(file) {
        if (!file.type.match(/image.*/)) {
            console.log("Not a picture !");
            Upload.initiate(file);
        } else {
            var reader = new FileReader();
            reader.readAsDataURL(file);

            reader.onload = function (ev) {
                Upload.compress(ev.target.result, file);
            };
        };
    },

    compress : function(src, file) {
        var image = new Image();
        image.onload = function()
        {
            var limit = 1920;

            var ratio = Math.min(limit / image.naturalWidth, limit / image.naturalHeight);

            var width = image.naturalWidth;
            var height = image.naturalHeight;

            if(ratio < 1 || file.size > 320000) {
                if(ratio < 1) {
                    width = Math.round(width*ratio);
                    height = Math.round(height*ratio);
                }

                var canvas = document.createElement('canvas');
                canvas.width = width;
                canvas.height = height;

                canvas.getContext("2d").drawImage(image, 0, 0, width, height);

                if(typeof canvas.toBlob == 'function') {
                    if(file.type != 'image/jpeg') {
                        Upload.name += '.jpg';
                    }

                    canvas.toBlob(
                        function (blob) {
                            Upload.initiate(blob);
                        },
                        'image/jpeg',
                        0.85
                    );
                } else {
                    Upload.initiate(file);
                }
            } else {
                Upload.initiate(file);
            }
        }
        image.src = src;
    },

    initiate : function(file) {
        Upload.file = file;

        Upload_ajaxSend({
            name: Upload.name,
            size: file.size,
            type: file.type
        });
    },

    request : function(get, put) {
        Upload.get = get;

        Upload.xhr = new XMLHttpRequest();

        Upload.xhr.upload.addEventListener('progress', function(evt) {
            var percent = Math.floor(evt.loaded/evt.total*100);
            var progress = document.querySelector('#dialog ul li p');
            if(progress) progress.innerHTML = percent + '%';
        }, false);

        Upload.xhr.onreadystatechange = function() {
            if(Upload.xhr.readyState == 4
            && Upload.xhr.status == 200) {
                Dialog.clear();
                Upload.launchAttached();
            }

            if(Upload.xhr.readyState == 4
            && Upload.xhr.status != 200) {
                Upload_ajaxFailed();
            }
        }

        Upload.xhr.open("PUT", put, true);

        Upload.xhr.setRequestHeader('Content-Type', 'text/plain');
        Upload.xhr.send(Upload.file);
    }
}

MovimWebsocket.attach(function()
{
});
<|endoftext|>


<commit_before>// The CustomElementRegistry Interface
// WHATWG - https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-api
//
// The Custom Elements Spec
// WHATWG- https://html.spec.whatwg.org/multipage/custom-elements.htm

window.customElements = ( function () { return (function () {
    function CustomElementRegistry () {}

    var staticAccessors = { registrants: {} };

    CustomElementRegistry.define = function (tag, element) {

    console.log ('Snuggsi definition')

    ('loading' === document.readyState)
      && document.addEventListener
        ('DOMContentLoaded',
          this.register (tag, element))
  };

  staticAccessors.registrants.get = function () {
    return registrants
  };

  CustomElementRegistry.register = function (name, klass) {
    var this$1 = this;

    this.registrants [name] = klass

    return function (event) {

      var
        selected  =
          document.body
            .querySelectorAll (name)

      , instances = []
          .slice
          .call (selected)
          .map  (this$1.upgrade)
    }
  };

  CustomElementRegistry.upgrade = function (element) {
    console.log
      ('ugrading element',
       element.localName)
  };

    Object.defineProperties( CustomElementRegistry, staticAccessors );

    return CustomElementRegistry;
  }())

&& null; }

)()

// HTMLElement Swizzle - To swizzle a method is to change a class’s dispatch table in order to resolve messages from an existing selector to a different implementation, while aliasing the original method implementation to a new selector.

var HTMLCustomElement = function () {
  return new String ('snuggsi HTMLCustomElement')
};

//window.HTMLElement = ((HTMLElement, klass) => {

//  return Object
//    .setPrototypeOf (klass, HTMLElement.prototype)
//})

//(window.HTMLElement, function HTMLElement () {})

var Foo = (function (HTMLElement) {
  function Foo () {
    HTMLElement.apply(this, arguments);
  }if ( HTMLElement ) Foo.__proto__ = HTMLElement;
  Foo.prototype = Object.create( HTMLElement && HTMLElement.prototype );
  Foo.prototype.constructor = Foo;

  return Foo;
}(HTMLElement));

var HTMLLinkElement = function

  // http://w3c.github.io/webcomponents/spec/imports/#h-interface-import

(tag) {

  var
    evt = new Event ('load')

  , target = document.querySelector // use CSS :any ?
      ('link[href*='+tag+'][rel=import]')

  // https://github.com/webcomponents/html-imports#htmlimports
  ;(evt.target = target)
    && window.HTMLImports
    && !!! window.HTMLImports.useNative
    && HTMLImports.whenReady ( function (_) { return target.dispatchEvent (evt); } ) // eww

  return target
}

var TokenList = function (node) {

  this
    .sift (node)
    .map(this.tokenize, this)
};

TokenList.prototype.tokenize = function (node) {
    var this$1 = this;

  var
    insert = function (node) { return function (symbol) { return (this$1 [symbol] = this$1 [symbol] || []).push (node); }; }

  void (node.text = node.textContent)
    .match (/([^{]*?)(\w|#)(?=\})/g)
    .map (insert (node))
};

TokenList.prototype.sift = function (node) {

  var
    nodes = []
  , expression = /{(\w+|#)}/

  , visit = function (node) { return node.nodeType === Node.TEXT_NODE
        ? TEXT_NODE (node)
        : ELEMENT_NODE (node.attributes)
      && NodeFilter.FILTER_REJECT; } // We don't need 'em

  , TEXT_NODE = function (node) { return expression.test (node.textContent)
      && nodes.push (node); }

  , ELEMENT_NODE = function (attrs) { return Array
        .from (attrs)
        .map(function (attr) { return expression.test (attr.value) && nodes.push (attr); }); }

  , walker =
      document.createNodeIterator
        (node, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, visit)
        // by default breaks on template YAY! 🎉

  while (walker.nextNode ()) { 0 } // Walk all nodes and do nothing.

  return nodes
};

TokenList.prototype.bind = function (context) {
    var this$1 = this;

  var
    keys = Object.keys (this)

  , reset = function (symbol) { return this$1 [symbol].map
        (function (node) { return (node.textContent = node.text) && symbol; }); }

  , replace =
      function (symbol, token) {
            if ( token === void 0 ) token = '{'+symbol+'}';

            return function (item) { return (item.textContent = item.textContent.replace (token, context [symbol])); };
    }

  keys.map (reset)

  for (var symbol$1 in this$1)
    { this$1 [symbol$1]
      .map (replace (symbol$1)) }
};

// INTERESTING! Converting `Template` to a class increases size by ~16 octets

var Template = HTMLTemplateElement = function (template) {

  template =
    (typeof template == 'string')
      ? document.querySelector ('template[name='+template+']')
      : template

  // create shallow clone using `.getOwnPropertyDescriptors`
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors#Examples
  // https://docs.microsoft.com/en-us/scripting/javascript/reference/object-getownpropertydescriptor-function-javascript
  // NO IE SUPPORT!!!!
  return Object.assign (template, { bind: bind } )

  function bind (context) {

    var
      html     = ''
    , template = this.innerHTML
    , contexts =
        (ref = []).concat.apply ( ref, [context] )
        // https://dom.spec.whatwg.org/#converting-nodes-into-a-node

    , keys =
        Object
          .keys (contexts [0] || [])    // memoize keys
          .concat (['#', 'self']) // add helper keys

    , tokens   = keys.map (function (key) { return '{'+key+'}'; }) // memoize tokens
    , fragment = document.createElement ('template')

    , deposit = function (context, index) {
        var clone = template

        context = (typeof context  === 'object')
          ? context : { self: context }

        context ['#'] = index

        for (var i=0; i<tokens.length; i++)
          { clone = clone
            .split (tokens [i])
            .join  (context [keys [i]]) }

        return clone
      }

    void (this.dependents || [])
      .map (function (dependent) { return dependent.remove (); })

    for (var i=0, final = ''; i<contexts.length; i++)
      { html += deposit (contexts [i], i) }

    fragment.innerHTML = html

    this.dependents = Array.from // non-live
      (fragment.content.childNodes)

    (ref$1 = this).after.apply ( ref$1, this.dependents )
    var ref;
    var ref$1;
  }
}

var EventTarget = function (HTMLElement) { return ((function (HTMLElement) {
    function anonymous () {
      HTMLElement.apply(this, arguments);
    }

    if ( HTMLElement ) anonymous.__proto__ = HTMLElement;
    anonymous.prototype = Object.create( HTMLElement && HTMLElement.prototype );
    anonymous.prototype.constructor = anonymous;

    anonymous.prototype.on = function ( event, handler ) {

    this.addEventListener
      (event, this.renderable (handler))
  };

  anonymous.prototype.renderable = function ( handler ) {
    var this$1 = this;

    return function (event, render) {
        if ( render === void 0 ) render = true;

        return (event.prevent = function (_) { return (render = false) && event.preventDefault (); })

      && handler.call (this$1, event) !== false // for `return false`

      && render && this$1.render ();
    } // check render availability
  };

    return anonymous;
  }(HTMLElement))); }

var ParentNode = function (Element) { return ((function (Element) {
    function anonymous () {
      Element.apply(this, arguments);
    }

    if ( Element ) anonymous.__proto__ = Element;
    anonymous.prototype = Object.create( Element && Element.prototype );
    anonymous.prototype.constructor = anonymous;

    anonymous.prototype.selectAll = function ( fragments ) {
    var tokens = [], len = arguments.length - 1;
    while ( len-- > 0 ) tokens[ len ] = arguments[ len + 1 ];

    fragments =
      (ref = []).concat.apply ( ref, [fragments] )

    var
      zip = function (selector, token) { return selector + token + fragments.shift (); }

    return Array
      .from
        (this.querySelectorAll
          (tokens.reduce (zip, fragments.shift ())))
    var ref;
  };

  anonymous.prototype.select = function ()
    // watch out for clobbering `HTMLInputElement.select ()`
    // https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/select
    {
    var selector = [], len = arguments.length;
    while ( len-- ) selector[ len ] = arguments[ len ];
 return (ref = this).selectAll.apply ( ref, selector ) [0]
    var ref; };

    return anonymous;
  }(Element))); }

//function comb
//  // ElementTraversal interface
//  // https://www.w3.org/TR/ElementTraversal/#interface-elementTraversal
//
//(parent) {
//  if (parent.hasChildNodes())
//    for (let node = parent.firstChild; node; node = node.nextSibling)
//      comb (node)
//}

var GlobalEventHandlers = function (Element) { return ((function (Element) {
    function anonymous () {
      Element.apply(this, arguments);
    }

    if ( Element ) anonymous.__proto__ = Element;
    anonymous.prototype = Object.create( Element && Element.prototype );
    anonymous.prototype.constructor = anonymous;

    anonymous.prototype.onconnect = function (event, target) {

    (target = event.target)
      && this.mirror (target.import.querySelector ('template'))

    Element.prototype.onconnect
      && Element.prototype.onconnect.call (this)

    this.tokens = new TokenList (this)
    this.render ()
  };

  // Reflection - https://en.wikipedia.org/wiki/Reflection_(computer_programming)
  // Type Introspection - https://en.wikipedia.org/wiki/Type_introspection
  //
  // In computing, type introspection is the ability of a program
  // to examine the type or properties of an object at runtime.
  // Some programming languages possess this capability.
  //
  // Introspection should not be confused with reflection,
  // which goes a step further and is the ability for a program to manipulate the values,
  // meta-data, properties and/or functions of an object at runtime.

  anonymous.prototype.reflect = function (handler, event) {
    ( event = ( handler.match (/^on(.+)$/) || [] ) [1] )

    && Object.keys // ensure W3C on event
     ( HTMLElement.prototype )
       .includes ( handler )

    && this.on (event, this [handler])
  };

  anonymous.prototype.register = function (node) {
    var this$1 = this;

    var
      register = function (event, handler) { return (handler = /{\s*(\w+)\s*}/.exec (node [event]))

        && ( handler = this$1 [ (handler || []) [1] ] )
        && ( node [event] = this$1.renderable (handler) ); }

    Array
      .from (node.attributes)
      .map (function (attr) { return attr.name; })
      .filter (function (name) { return /^on/.test (name); })
      .map (register)
  };

    return anonymous;
  }(Element))); }

var Custom = function (Element) { return ( (function (superclass) {
    function anonymous () {
  var this$1 = this;
 superclass.call (this)

    var
      descriptions =
        Object.getOwnPropertyDescriptors
          (Element.prototype)

    , bind = function (key) { return 'function' === typeof descriptions [key].value
        && (this$1 [key] = this$1 [key].bind (this$1)); }

    Object
      .keys (descriptions)
      .map (bind)

    Object
      .getOwnPropertyNames (Element.prototype)
      // POTENTIAL REDUNDANCY
      // Aren't `on` events set up in `.bind` on 20?
      // If so we are `.bind`ing to `this` on two iterations
      // of the same function
      .map (this.reflect, this)

    this.context = {}
    this.initialize && this.initialize ()
  }

    if ( superclass ) anonymous.__proto__ = superclass;
    anonymous.prototype = Object.create( superclass && superclass.prototype );
    anonymous.prototype.constructor = anonymous;

  anonymous.prototype.connectedCallback = function (link) {
    link = HTMLLinkElement
      (this.tagName.toLowerCase ())

    link
      ? link.addEventListener ('load', this.onconnect.bind (this))
      : this.onconnect (new Event ('load'))
  };

  anonymous.prototype.render = function () {
    var this$1 = this;

    this.tokens.bind (this)

    Array
      .from
        (this.selectAll ('template[name]'))

      .map
        (function (template) { return template.getAttribute ('name'); })

      .map
        (function (name) { return (new Template (name)).bind (this$1 [name]); })

    Array
      .from (this.selectAll ('*'))

      .concat ([this])

      .map (this.register, this)

    superclass.prototype.onidle && superclass.prototype.onidle.call (this)
  };

  anonymous.prototype.mirror = function (template, insert) {
    var this$1 = this;

    template = template.cloneNode (true)

    insert = function (replacement, name, slot) { return (name = replacement.getAttribute ('slot')) &&

      (slot = template.content.querySelector ('slot[name='+name+']'))
         // prefer to use replaceWith however support is sparse
         // https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/replaceWith
         // using `Node.parentNode` - https://developer.mozilla.org/en-US/docs/Web/API/Node/parentNode
         // & `Node.replaceChid` - https://developer.mozilla.org/en-US/docs/Web/API/Node/replaceChild
         // as is defined in (ancient) W3C DOM Level 1,2,3
         .parentNode
         .replaceChild (replacement, slot); }

    for (var i = 0, list = this$1.selectAll ('[slot]'); i < list.length; i += 1)
      {
      var replacement = list[i];

      insert (replacement)
    }

    Array
      .from (template.attributes)

      // skip swapping attribute if setting exists
      .filter (function (attr) { return !!! this$1.attributes [attr.name]; })

      .map  (function (attr) { return this$1.setAttribute (attr.name, attr.value); })

    this.innerHTML = template.innerHTML
  };

    return anonymous;
  }(( EventTarget ( ParentNode ( GlobalEventHandlers (Element) )))))); }

var ElementPrototype = window.Element.prototype // see bottom of this file

var Element = function (tag) {

  var constructor =// swizzle
    typeof tag === 'string'
      ? HTMLCustomElement
      : HTMLElement

    //https://gist.github.com/allenwb/53927e46b31564168a1d
    // https://github.com/w3c/webcomponents/issues/587#issuecomment-271031208
    // https://github.com/w3c/webcomponents/issues/587#issuecomment-254017839

    return function (Element) {

      console.warn ('tag', tag)
      console.warn ('Element', Element)
      console.warn ('Element.prototype', Element.prototype)
      console.warn ('Element.prototype.constructor', Element.prototype.constructor)
      Element.prototype.constructor = constructor
      console.warn ('Element.prototype.constructor', Element.prototype.constructor)
      console.warn ('Element.constructor', Element.constructor)
      console.warn ('constructor', constructor)
      console.warn ('constructor.prototype', constructor.prototype)

      (ref = window.customElements).define.apply
        ( ref, (ref$1 = []).concat.apply ( ref$1, [tag]).concat( [Custom (Element)] ))
      var ref;
      var ref$1;

    }
}

// Assign `window.Element.prototype` in case of feature checking on `Element`
Element.prototype = ElementPrototype
  // http://2ality.com/2013/09/window.html
  // http://tobyho.com/2013/03/13/window-prop-vs-global-var
  // https://github.com/webcomponents/webcomponentsjs/blob/master/webcomponents-es5-loader.js#L19

<commit_msg>:muscle: bump TRANSPILED distribution dist/snuggsi.js<commit_after>// The CustomElementRegistry Interface
// WHATWG - https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-api
//
// The Custom Elements Spec
// WHATWG- https://html.spec.whatwg.org/multipage/custom-elements.htm

(function (registry, define) {
if ( define === void 0 ) define = registry.define.bind (registry);

var CustomElementRegistry = function () {};

CustomElementRegistry.define = function (name, klass, constructor) {

  console.warn ('Snuggsi', name, constructor, klass);

  ('loading' === document.readyState)
    && document.addEventListener
      ('DOMContentLoaded',
        this.register (name, klass))
};

CustomElementRegistry.register = function (name, klass) {
    var this$1 = this;

  this [name] = klass

  define &&
    define (name, this [name])

  return function (event) {

    var
      selected=
        document.body
          .querySelectorAll (name)

    , instances = []
        .slice
        .call (selected)
        .map(this$1.upgrade)
  }
};

CustomElementRegistry.upgrade = function (element) {
  console.log
    ('ugrading element',
     element.localName)
};

registry.define =
  CustomElementRegistry.define
    .bind (CustomElementRegistry)

})

(window.customElements = window.customElements || {})

// http://nshipster.com/method-swizzling/
// HTMLElement Swizzle - To swizzle a method is to change a class’s dispatch table in order to resolve messages from an existing selector to a different implementation, while aliasing the original method implementation to a new selector.

var HTMLCustomElement = function () {
  return new String ('snuggsi HTMLCustomElement')
};

//window.HTMLElement = ((HTMLElement, klass) => {

//  return Object
//    .setPrototypeOf (klass, HTMLElement.prototype)
//})

//(window.HTMLElement, function HTMLElement () {})

var HTMLLinkElement = function

  // http://w3c.github.io/webcomponents/spec/imports/#h-interface-import

(tag) {

  var
    evt = new Event ('load')

  , target = document.querySelector // use CSS :any ?
      ('link[href*='+tag+'][rel=import]')

  // https://github.com/webcomponents/html-imports#htmlimports
  ;(evt.target = target)
    && window.HTMLImports
    && !!! window.HTMLImports.useNative
    && HTMLImports.whenReady ( function (_) { return target.dispatchEvent (evt); } ) // eww

  return target
}

var TokenList = function (node) {

  this
    .sift (node)
    .map(this.tokenize, this)
};

TokenList.prototype.tokenize = function (node) {
    var this$1 = this;

  var
    insert = function (node) { return function (symbol) { return (this$1 [symbol] = this$1 [symbol] || []).push (node); }; }

  void (node.text = node.textContent)
    .match (/([^{]*?)(\w|#)(?=\})/g)
    .map (insert (node))
};

TokenList.prototype.sift = function (node) {

  var
    nodes = []
  , expression = /{(\w+|#)}/

  , visit = function (node) { return node.nodeType === Node.TEXT_NODE
        ? TEXT_NODE (node)
        : ELEMENT_NODE (node.attributes)
      && NodeFilter.FILTER_REJECT; } // We don't need 'em

  , TEXT_NODE = function (node) { return expression.test (node.textContent)
      && nodes.push (node); }

  , ELEMENT_NODE = function (attrs) { return Array
        .from (attrs)
        .map(function (attr) { return expression.test (attr.value) && nodes.push (attr); }); }

  , walker =
      document.createNodeIterator
        (node, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, visit)
        // by default breaks on template YAY! 🎉

  while (walker.nextNode ()) { 0 } // Walk all nodes and do nothing.

  return nodes
};

TokenList.prototype.bind = function (context) {
    var this$1 = this;

  var
    keys = Object.keys (this)

  , reset = function (symbol) { return this$1 [symbol].map
        (function (node) { return (node.textContent = node.text) && symbol; }); }

  , replace =
      function (symbol, token) {
            if ( token === void 0 ) token = '{'+symbol+'}';

            return function (item) { return (item.textContent = item.textContent.replace (token, context [symbol])); };
    }

  keys.map (reset)

  for (var symbol$1 in this$1)
    { this$1 [symbol$1]
      .map (replace (symbol$1)) }
};

// INTERESTING! Converting `Template` to a class increases size by ~16 octets

// https://github.com/webcomponents/template
var Template = HTMLTemplateElement = function (template) {

  template =
    (typeof template == 'string')
      ? document.querySelector ('template[name='+template+']')
      : template

  // create shallow clone using `.getOwnPropertyDescriptors`
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors#Examples
  // https://docs.microsoft.com/en-us/scripting/javascript/reference/object-getownpropertydescriptor-function-javascript
  // NO IE SUPPORT!!!!
  return Object.assign (template, { bind: bind } )

  function bind (context) {

    var
      html     = ''
    , template = this.innerHTML
    , contexts =
        (ref = []).concat.apply ( ref, [context] )
        // https://dom.spec.whatwg.org/#converting-nodes-into-a-node

    , keys =
        Object
          .keys (contexts [0] || [])    // memoize keys
          .concat (['#', 'self']) // add helper keys

    , tokens   = keys.map (function (key) { return '{'+key+'}'; }) // memoize tokens
    , fragment = document.createElement ('template')

    , deposit = function (context, index) {
        var clone = template

        context = (typeof context  === 'object')
          ? context : { self: context }

        context ['#'] = index

        for (var i=0; i<tokens.length; i++)
          { clone = clone
            .split (tokens [i])
            .join  (context [keys [i]]) }

        return clone
      }

    void (this.dependents || [])
      .map (function (dependent) { return dependent.remove (); })

    for (var i=0, final = ''; i<contexts.length; i++)
      { html += deposit (contexts [i], i) }

    fragment.innerHTML = html

    this.dependents = Array.from // non-live
      (fragment.content.childNodes)

    (ref$1 = this).after.apply ( ref$1, this.dependents )
    var ref;
    var ref$1;
  }
}

var EventTarget = function (HTMLElement) { return ((function (HTMLElement) {
    function anonymous () {
      HTMLElement.apply(this, arguments);
    }

    if ( HTMLElement ) anonymous.__proto__ = HTMLElement;
    anonymous.prototype = Object.create( HTMLElement && HTMLElement.prototype );
    anonymous.prototype.constructor = anonymous;

    anonymous.prototype.on = function ( event, handler ) {

    this.addEventListener
      (event, this.renderable (handler))
  };

  anonymous.prototype.renderable = function ( handler ) {
    var this$1 = this;

    // BIG BUG IN IE!!!
    //
    // https://connect.microsoft.com/IE/feedback/details/790389/event-defaultprevented-returns-false-after-preventdefault-was-called
    //
    // https://github.com/webcomponents/webcomponents-platform/blob/master/webcomponents-platform.js#L16

    return function (event, render) {
        if ( render === void 0 ) render = true;

        return (event.prevent = function (_) { return (render = false) && event.preventDefault (); })

      && handler.call (this$1, event) !== false // for `return false`

      && render && this$1.render ();
    } // check render availability
  };

    return anonymous;
  }(HTMLElement))); }

var ParentNode = function (Element) { return ((function (Element) {
    function anonymous () {
      Element.apply(this, arguments);
    }

    if ( Element ) anonymous.__proto__ = Element;
    anonymous.prototype = Object.create( Element && Element.prototype );
    anonymous.prototype.constructor = anonymous;

    anonymous.prototype.selectAll = function ( fragments ) {
    var tokens = [], len = arguments.length - 1;
    while ( len-- > 0 ) tokens[ len ] = arguments[ len + 1 ];

    fragments =
      (ref = []).concat.apply ( ref, [fragments] )

    var
      zip = function (selector, token) { return selector + token + fragments.shift (); }

    return Array
      .from
        (this.querySelectorAll
          (tokens.reduce (zip, fragments.shift ())))
    var ref;
  };

  anonymous.prototype.select = function ()
    // watch out for clobbering `HTMLInputElement.select ()`
    // https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/select
    {
    var selector = [], len = arguments.length;
    while ( len-- ) selector[ len ] = arguments[ len ];
 return (ref = this).selectAll.apply ( ref, selector ) [0]
    var ref; };

    return anonymous;
  }(Element))); }

//function comb
//  // ElementTraversal interface
//  // https://www.w3.org/TR/ElementTraversal/#interface-elementTraversal
//
//(parent) {
//  if (parent.hasChildNodes())
//    for (let node = parent.firstChild; node; node = node.nextSibling)
//      comb (node)
//}

var GlobalEventHandlers = function (Element) { return ((function (Element) {
    function anonymous () {
      Element.apply(this, arguments);
    }

    if ( Element ) anonymous.__proto__ = Element;
    anonymous.prototype = Object.create( Element && Element.prototype );
    anonymous.prototype.constructor = anonymous;

    anonymous.prototype.onconnect = function (event, target) {

    (target = event.target)
      && this.mirror (target.import.querySelector ('template'))

    Element.prototype.onconnect
      && Element.prototype.onconnect.call (this)

    this.tokens = new TokenList (this)
    this.render ()
  };

  // Reflection - https://en.wikipedia.org/wiki/Reflection_(computer_programming)
  // Type Introspection - https://en.wikipedia.org/wiki/Type_introspection
  //
  // In computing, type introspection is the ability of a program
  // to examine the type or properties of an object at runtime.
  // Some programming languages possess this capability.
  //
  // Introspection should not be confused with reflection,
  // which goes a step further and is the ability for a program to manipulate the values,
  // meta-data, properties and/or functions of an object at runtime.

  anonymous.prototype.reflect = function (handler, event) {
    ( event = ( handler.match (/^on(.+)$/) || [] ) [1] )

    && Object.keys // ensure W3C on event
     ( HTMLElement.prototype )
       .includes ( handler )

    && this.on (event, this [handler])
  };

  anonymous.prototype.register = function (node) {
    var this$1 = this;

    var
      register = function (event, handler) { return (handler = /{\s*(\w+)\s*}/.exec (node [event]))

        && ( handler = this$1 [ (handler || []) [1] ] )
        && ( node [event] = this$1.renderable (handler) ); }

    Array
      .from (node.attributes)
      .map (function (attr) { return attr.name; })
      .filter (function (name) { return /^on/.test (name); })
      .map (register)
  };

    return anonymous;
  }(Element))); }

var Custom = function (Element) { return ( (function (superclass) {
    function anonymous () {
  var this$1 = this;
 superclass.call (this)

    var
      descriptions =
        Object.getOwnPropertyDescriptors
          (Element.prototype)

    , bind = function (key) { return 'function' === typeof descriptions [key].value
        && (this$1 [key] = this$1 [key].bind (this$1)); }

    Object
      .keys (descriptions)
      .map (bind)

    Object
      .getOwnPropertyNames (Element.prototype)
      // POTENTIAL REDUNDANCY
      // Aren't `on` events set up in `.bind` on 20?
      // If so we are `.bind`ing to `this` on two iterations
      // of the same function
      .map (this.reflect, this)

    this.context = {}
    this.initialize && this.initialize ()
  }

    if ( superclass ) anonymous.__proto__ = superclass;
    anonymous.prototype = Object.create( superclass && superclass.prototype );
    anonymous.prototype.constructor = anonymous;

  anonymous.prototype.connectedCallback = function (link) {
    link = HTMLLinkElement
      (this.tagName.toLowerCase ())

    link
      ? link.addEventListener ('load', this.onconnect.bind (this))
      : this.onconnect (new Event ('load'))
  };

  anonymous.prototype.render = function () {
    var this$1 = this;

    this.tokens.bind (this)

    Array
      .from
        (this.selectAll ('template[name]'))

      .map
        (function (template) { return template.getAttribute ('name'); })

      .map
        (function (name) { return (new Template (name)).bind (this$1 [name]); })

    Array
      .from (this.selectAll ('*'))

      .concat ([this])

      .map (this.register, this)

    superclass.prototype.onidle && superclass.prototype.onidle.call (this)
  };

  anonymous.prototype.mirror = function (template, insert) {
    var this$1 = this;

    template = template.cloneNode (true)

    insert = function (replacement, name, slot) { return (name = replacement.getAttribute ('slot')) &&

      (slot = template.content.querySelector ('slot[name='+name+']'))
         // prefer to use replaceWith however support is sparse
         // https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/replaceWith
         // using `Node.parentNode` - https://developer.mozilla.org/en-US/docs/Web/API/Node/parentNode
         // & `Node.replaceChid` - https://developer.mozilla.org/en-US/docs/Web/API/Node/replaceChild
         // as is defined in (ancient) W3C DOM Level 1,2,3
         .parentNode
         .replaceChild (replacement, slot); }

    for (var i = 0, list = this$1.selectAll ('[slot]'); i < list.length; i += 1)
      {
      var replacement = list[i];

      insert (replacement)
    }

    Array
      .from (template.attributes)

      // skip swapping attribute if setting exists
      .filter (function (attr) { return !!! this$1.attributes [attr.name]; })

      .map  (function (attr) { return this$1.setAttribute (attr.name, attr.value); })

    this.innerHTML = template.innerHTML
  };

    return anonymous;
  }(( EventTarget ( ParentNode ( GlobalEventHandlers (Element) )))))); }

var ElementPrototype = window.Element.prototype // see bottom of this file

var Element = function (tag) {

  var constructor =// swizzle
    typeof tag === 'string'
      ? HTMLCustomElement
      : HTMLElement

    //https://gist.github.com/allenwb/53927e46b31564168a1d
    // https://github.com/w3c/webcomponents/issues/587#issuecomment-271031208
    // https://github.com/w3c/webcomponents/issues/587#issuecomment-254017839

    return function (Element) {

      console.warn ('tag', tag)
      console.warn ('Element', Element)
      console.warn ('Element.prototype', Element.prototype)
      console.warn ('Element.prototype.constructor', Element.prototype.constructor)
      console.warn ('Element.constructor', Element.constructor)
      console.warn ('constructor', constructor)
      console.warn ('constructor.prototype', constructor.prototype)

      void (ref = window.customElements).define.apply
        ( ref, (ref$1 = []).concat.apply ( ref$1, [tag]).concat( [Custom (Element)], [constructor] ))
      var ref;
      var ref$1;

    }
}

// Assign `window.Element.prototype` in case of feature checking on `Element`
Element.prototype = ElementPrototype
  // http://2ality.com/2013/09/window.html
  // http://tobyho.com/2013/03/13/window-prop-vs-global-var
  // https://github.com/webcomponents/webcomponentsjs/blob/master/webcomponents-es5-loader.js#L19

<|endoftext|>


<commit_before><commit_msg>Initiate database pool from DATABASE_URL in Heroku.<commit_after><|endoftext|>