<commit_before>
// ------------------------------------------------------------------------------------------- //
// ----------------------------------------- Schema ----------------------------------------- //
// ------------------------------------------------------------------------------------------- //

SimpleSchema.extendOptions({
  editable: Match.Optional(Boolean) // editable: true means the field can be edited by the document's owner
});

postSchemaObject = {
  _id: {
    type: String,
    optional: true,
    autoform: {
      omit: true
    }
  },
  createdAt: {
    type: Date,
    optional: true,
    autoform: {
      omit: true
    }
  },
  postedAt: {
    type: Date,
    optional: true,
    autoform: {
      group: 'admin',
      type: "bootstrap-datetimepicker"
    }
  },
  url: {
    type: String,
    label: "URL",
    optional: true,
    autoform: {
      editable: true,
      type: "bootstrap-url"
    }
  },
  title: {
    type: String,
    optional: false,
    label: "Title",
    editable: true,
    autoform: {
      editable: true
    }
  },
  body: {
    type: String,
    optional: true,
    editable: true,
    autoform: {
      editable: true,
      rows: 5
    }
  },
  htmlBody: {
    type: String,
    optional: true,
    autoform: {
      omit: true
    }
  },
  viewCount: {
    type: Number,
    optional: true,
    autoform: {
      omit: true
    }
  },
  commentCount: {
    type: Number,
    optional: true,
    autoform: {
      omit: true
    }
  },
  commenters: {
    type: [String],
    optional: true,
    autoform: {
      omit: true
    }
  },
  lastCommentedAt: {
    type: Date,
    optional: true,
    autoform: {
      omit: true
    }
  },
  clickCount: {
    type: Number,
    optional: true,
    autoform: {
      omit: true
    }
  },
  baseScore: {
    type: Number,
    decimal: true,
    optional: true,
    autoform: {
      omit: true
    }
  },
  upvotes: {
    type: Number,
    optional: true,
    autoform: {
      omit: true
    }
  },
  upvoters: {
    type: [String], // XXX
    optional: true,
    autoform: {
      omit: true
    }
  },
  downvotes: {
    type: Number,
    optional: true,
    autoform: {
      omit: true
    }
  },
  downvoters: {
    type: [String], // XXX
    optional: true,
    autoform: {
      omit: true
    }
  },
  score: {
    type: Number,
    decimal: true,
    optional: true,
    autoform: {
      omit: true
    }
  },
  status: {
    type: Number,
    optional: true,
    autoValue: function () {
      // only provide a default value
      // 1) this is an insert operation
      // 2) status field is not set in the document being inserted
      var user = Meteor.users.findOne(this.userId);
      if (this.isInsert && !this.isSet)
        return getDefaultPostStatus(user);
    },
    autoform: {
      noselect: true,
      options: postStatuses,
      group: 'admin'
    }
  },
  sticky: {
    type: Boolean,
    optional: true,
    defaultValue: false,
    autoform: {
      group: 'admin',
      leftLabel: "Sticky"
    }
  },
  inactive: {
    type: Boolean,
    optional: true,
    autoform: {
      omit: true
    }
  },
  author: {
    type: String,
    optional: true,
    autoform: {
      omit: true
    }
  },
  userId: {
    type: String, // XXX
    optional: true,
    autoform: {
      group: 'admin',
      options: function () {
        return Meteor.users.find().map(function (user) {
          return {
            value: user._id,
            label: getDisplayName(user)
          }
        });
      }
    }
  }
};

// add any extra properties to postSchemaObject (provided by packages for example)
_.each(addToPostSchema, function(item){
  postSchemaObject[item.propertyName] = item.propertySchema;
});

Posts = new Meteor.Collection("posts");

PostSchema = new SimpleSchema(postSchemaObject);
Posts.attachSchema(PostSchema);

// Posts.deny({
//   update: function(userId, post, fieldNames) {
//     if(isAdminById(userId))
//       return false;
//     // deny the update if it contains something other than the following fields
//     return (_.without(fieldNames, 'title', 'url', 'body', 'shortUrl', 'shortTitle', 'categories').length > 0);
//   }
// });

// Posts.allow({
//   update: canEditById,
//   remove: canEditById
// });

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

getPostProperties = function (post) {

  var postAuthor = Meteor.users.findOne(post.userId);
  var p = {
    postAuthorName : getDisplayName(postAuthor),
    postTitle : cleanUp(post.title),
    profileUrl: getProfileUrlBySlugOrId(post.userId),
    postUrl: getPostPageUrl(post),
    thumbnailUrl: post.thumbnailUrl,
    linkUrl: !!post.url ? getOutgoingUrl(post.url) : getPostPageUrl(post._id)
  };

  if(post.url)
    p.url = post.url;

  if(post.htmlBody)
    p.htmlBody = post.htmlBody;

  return p;
};

// default status for new posts
getDefaultPostStatus = function (user) {
  if (isAdmin(user) || !getSetting('requirePostsApproval', false)) {
    // if user is admin, or else post approval is not required
    return STATUS_APPROVED
  } else {
    // else
    return STATUS_PENDING
  }
}

getPostPageUrl = function(post){
  return getSiteUrl()+'posts/'+post._id;
};

getPostEditUrl = function(id){
  return getSiteUrl()+'posts/'+id+'/edit';
};

// for a given post, return its link if it has one, or else its post page URL
getPostLink = function (post) {
  return !!post.url ? getOutgoingUrl(post.url) : getPostPageUrl(post);
};

checkForPostsWithSameUrl = function (url) {

  // check that there are no previous posts with the same link in the past 6 months
  var sixMonthsAgo = moment().subtract(6, 'months').toDate();
  var postWithSameLink = Posts.findOne({url: url, postedAt: {$gte: sixMonthsAgo}});

  if(typeof postWithSameLink !== 'undefined'){
    Meteor.call('upvotePost', postWithSameLink);
    // note: error.details returns undefined on the client, so add post ID to reason
    throw new Meteor.Error('603', i18n.t('this_link_has_already_been_posted') + '|' + postWithSameLink._id, postWithSameLink._id);
  }
}

// ------------------------------------------------------------------------------------------- //
// ------------------------------------------ Hooks ------------------------------------------ //
// ------------------------------------------------------------------------------------------- //

Posts.before.insert(function (userId, doc) {
  if(Meteor.isServer && !!doc.body)
    doc.htmlBody = sanitize(marked(doc.body));
});

Posts.before.update(function (userId, doc, fieldNames, modifier, options) {
  // if body is being modified, update htmlBody too
  if (Meteor.isServer && modifier.$set && modifier.$set.body) {
    modifier.$set = modifier.$set || {};
    modifier.$set.htmlBody = sanitize(marked(modifier.$set.body));
  }
});

// ------------------------------------------------------------------------------------------- //
// ----------------------------------------- Methods ----------------------------------------- //
// ------------------------------------------------------------------------------------------- //

postClicks = [];
postViews = [];

Meteor.methods({

  submitPost: function(post){
    var title = cleanUp(post.title),
        body = post.body,
        userId = this.userId,
        user = Meteor.users.findOne(userId),
        timeSinceLastPost=timeSinceLast(user, Posts),
        numberOfPostsInPast24Hours=numberOfItemsInPast24Hours(user, Posts),
        postInterval = Math.abs(parseInt(getSetting('postInterval', 30))),
        maxPostsPer24Hours = Math.abs(parseInt(getSetting('maxPostsPerDay', 30))),
        postId = '';

    // ------------------------------ Checks ------------------------------ //

    // check that user can post
    if (!user || !canPost(user))
      throw new Meteor.Error(601, i18n.t('you_need_to_login_or_be_invited_to_post_new_stories'));

    // check that user provided a title
    if(!post.title)
      throw new Meteor.Error(602, i18n.t('please_fill_in_a_title'));

    // check that there are no posts with the same URL
    if(!!post.url)
      checkForPostsWithSameUrl(post.url);

    // --------------------------- Rate Limiting -------------------------- //

    if(!isAdmin(Meteor.user())){
      // check that user waits more than X seconds between posts
      if(!this.isSimulation && timeSinceLastPost < postInterval)
        throw new Meteor.Error(604, i18n.t('please_wait')+(postInterval-timeSinceLastPost)+i18n.t('seconds_before_posting_again'));

      // check that the user doesn't post more than Y posts per day
      if(!this.isSimulation && numberOfPostsInPast24Hours > maxPostsPer24Hours)
        throw new Meteor.Error(605, i18n.t('sorry_you_cannot_submit_more_than')+maxPostsPer24Hours+i18n.t('posts_per_day'));
    }

    // ------------------------------ Properties ------------------------------ //

    // Basic Properties
    properties = {
      title: title,
      body: body,
      userId: userId,
      author: getDisplayNameById(userId),
      upvotes: 0,
      downvotes: 0,
      commentCount: 0,
      clickCount: 0,
      viewCount: 0,
      baseScore: 0,
      score: 0,
      inactive: false
    };

    // UserId
    if(isAdmin(Meteor.user()) && !!post.userId){ // only let admins post as other users
      properties.userId = post.userId;
    }

    // Status
    if(!!post.status && isAdmin(Meteor.user())){
      // if a custom status has been set, and user is admin, use that
      properties.status = post.status;
    }else{
      // else use default status
      properties.status = getDefaultPostStatus(Meteor.user());
    }

    // CreatedAt
    properties.createdAt = new Date();

    // PostedAt
    if(properties.status == 2){ // only set postedAt if post is approved
      if(isAdmin(Meteor.user()) && !!post.postedAt){ // if user is admin and a custom postDate has been set
        properties.postedAt = post.postedAt;
      }else{ // else use current time
        properties.postedAt = new Date();
      }
    }

    post = _.extend(post, properties);

    // ------------------------------ Callbacks ------------------------------ //

    // run all post submit server callbacks on post object successively
    post = postSubmitMethodCallbacks.reduce(function(result, currentFunction) {
        return currentFunction(result);
    }, post);

    // ------------------------------ Insert ------------------------------ //

    // console.log(post)
    post._id = Posts.insert(post);

    // ------------------------------ Callbacks ------------------------------ //

    // run all post submit server callbacks on post object successively
    post = postAfterSubmitMethodCallbacks.reduce(function(result, currentFunction) {
        return currentFunction(result);
    }, post);

    // ------------------------------ After Insert ------------------------------ //

    // increment posts count
    Meteor.users.update({_id: userId}, {$inc: {postCount: 1}});

    var postAuthor =  Meteor.users.findOne(post.userId);

    Meteor.call('upvotePost', post, postAuthor);

    return post;
  },

  editPost: function (postId, updateObject) {

    var user = Meteor.user();

    // console.log(updateObject)

    // ------------------------------ Checks ------------------------------ //

    // check that user can edit
    if (!user || !canEdit(user, Posts.findOne(postId)))
      throw new Meteor.Error(601, i18n.t('sorry_you_cannot_edit_this_post'));

    // ------------------------------ Callbacks ------------------------------ //

    // run all post submit server callbacks on updateObject successively
    updateObject = postEditMethodCallbacks.reduce(function(result, currentFunction) {
        return currentFunction(result);
    }, updateObject);

    console.log(updateObject)

    // ------------------------------ Update ------------------------------ //

    Posts.update(postId, updateObject);

    // ------------------------------ Callbacks ------------------------------ //

    // run all post submit server callbacks on updateObject successively
    updateObject = postAfterEditMethodCallbacks.reduce(function(result, currentFunction) {
        return currentFunction(result);
    }, updateObject);

    // ------------------------------ After Update ------------------------------ //

    return Posts.findOne(postId);

  },

  setPostedAt: function(post, customPostedAt){

    var postedAt = new Date(); // default to current date and time

    if(isAdmin(Meteor.user()) && typeof customPostedAt !== 'undefined') // if user is admin and a custom datetime has been set
      postedAt = customPostedAt;

    Posts.update(post._id, {$set: {postedAt: postedAt}});
  },

  approvePost: function(post){
    if(isAdmin(Meteor.user())){
      var set = {status: 2};

      // unless post is already scheduled and has a postedAt date, set its postedAt date to now
      if (!post.postedAt)
        set.postedAt = new Date();

      var result = Posts.update(post._id, {$set: set}, {validate: false});
    }else{
      flashMessage('You need to be an admin to do that.', "error");
    }
  },

  unapprovePost: function(post){
    if(isAdmin(Meteor.user())){
      Posts.update(post._id, {$set: {status: 1}});
    }else{
      flashMessage('You need to be an admin to do that.', "error");
    }
  },

  increasePostViews: function(postId, sessionId){
    this.unblock();

    // only let users increment a post's view counter once per session
    var view = {_id: postId, userId: this.userId, sessionId: sessionId};

    if(_.where(postViews, view).length == 0){
        postViews.push(view);
        Posts.update(postId, { $inc: { viewCount: 1 }});
    }
  },

  increasePostClicks: function(postId, sessionId){
    this.unblock();

    // only let clients increment a post's click counter once per session
    var click = {_id: postId, userId: this.userId, sessionId: sessionId};

    if(_.where(postClicks, click).length == 0){
      postClicks.push(click);
      Posts.update(postId, { $inc: { clickCount: 1 }});
    }
  },

  deletePostById: function(postId) {
    // remove post comments
    // if(!this.isSimulation) {
    //   Comments.remove({post: postId});
    // }
    // NOTE: actually, keep comments after all

    var post = Posts.findOne({_id: postId});

    if(!Meteor.userId() || !canEditById(Meteor.userId(), post)) throw new Meteor.Error(606, 'You need permission to edit or delete a post');

    // decrement post count
    Meteor.users.update({_id: post.userId}, {$inc: {postCount: -1}});

    // delete post
    Posts.remove(postId);
  }

});
<commit_msg>splitting submitPost into client/server and server functions<commit_after>
// ------------------------------------------------------------------------------------------- //
// ----------------------------------------- Schema ----------------------------------------- //
// ------------------------------------------------------------------------------------------- //

SimpleSchema.extendOptions({
  editable: Match.Optional(Boolean) // editable: true means the field can be edited by the document's owner
});

postSchemaObject = {
  _id: {
    type: String,
    optional: true,
    autoform: {
      omit: true
    }
  },
  createdAt: {
    type: Date,
    optional: true,
    autoform: {
      omit: true
    }
  },
  postedAt: {
    type: Date,
    optional: true,
    autoform: {
      group: 'admin',
      type: "bootstrap-datetimepicker"
    }
  },
  url: {
    type: String,
    label: "URL",
    optional: true,
    autoform: {
      editable: true,
      type: "bootstrap-url"
    }
  },
  title: {
    type: String,
    optional: false,
    label: "Title",
    editable: true,
    autoform: {
      editable: true
    }
  },
  body: {
    type: String,
    optional: true,
    editable: true,
    autoform: {
      editable: true,
      rows: 5
    }
  },
  htmlBody: {
    type: String,
    optional: true,
    autoform: {
      omit: true
    }
  },
  viewCount: {
    type: Number,
    optional: true,
    autoform: {
      omit: true
    }
  },
  commentCount: {
    type: Number,
    optional: true,
    autoform: {
      omit: true
    }
  },
  commenters: {
    type: [String],
    optional: true,
    autoform: {
      omit: true
    }
  },
  lastCommentedAt: {
    type: Date,
    optional: true,
    autoform: {
      omit: true
    }
  },
  clickCount: {
    type: Number,
    optional: true,
    autoform: {
      omit: true
    }
  },
  baseScore: {
    type: Number,
    decimal: true,
    optional: true,
    autoform: {
      omit: true
    }
  },
  upvotes: {
    type: Number,
    optional: true,
    autoform: {
      omit: true
    }
  },
  upvoters: {
    type: [String], // XXX
    optional: true,
    autoform: {
      omit: true
    }
  },
  downvotes: {
    type: Number,
    optional: true,
    autoform: {
      omit: true
    }
  },
  downvoters: {
    type: [String], // XXX
    optional: true,
    autoform: {
      omit: true
    }
  },
  score: {
    type: Number,
    decimal: true,
    optional: true,
    autoform: {
      omit: true
    }
  },
  status: {
    type: Number,
    optional: true,
    autoValue: function () {
      // only provide a default value
      // 1) this is an insert operation
      // 2) status field is not set in the document being inserted
      var user = Meteor.users.findOne(this.userId);
      if (this.isInsert && !this.isSet)
        return getDefaultPostStatus(user);
    },
    autoform: {
      noselect: true,
      options: postStatuses,
      group: 'admin'
    }
  },
  sticky: {
    type: Boolean,
    optional: true,
    defaultValue: false,
    autoform: {
      group: 'admin',
      leftLabel: "Sticky"
    }
  },
  inactive: {
    type: Boolean,
    optional: true,
    autoform: {
      omit: true
    }
  },
  author: {
    type: String,
    optional: true,
    autoform: {
      omit: true
    }
  },
  userId: {
    type: String, // XXX
    optional: true,
    autoform: {
      group: 'admin',
      options: function () {
        return Meteor.users.find().map(function (user) {
          return {
            value: user._id,
            label: getDisplayName(user)
          }
        });
      }
    }
  }
};

// add any extra properties to postSchemaObject (provided by packages for example)
_.each(addToPostSchema, function(item){
  postSchemaObject[item.propertyName] = item.propertySchema;
});

Posts = new Meteor.Collection("posts");

PostSchema = new SimpleSchema(postSchemaObject);
Posts.attachSchema(PostSchema);

// Posts.deny({
//   update: function(userId, post, fieldNames) {
//     if(isAdminById(userId))
//       return false;
//     // deny the update if it contains something other than the following fields
//     return (_.without(fieldNames, 'title', 'url', 'body', 'shortUrl', 'shortTitle', 'categories').length > 0);
//   }
// });

// Posts.allow({
//   update: canEditById,
//   remove: canEditById
// });

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

getPostProperties = function (post) {

  var postAuthor = Meteor.users.findOne(post.userId);
  var p = {
    postAuthorName : getDisplayName(postAuthor),
    postTitle : cleanUp(post.title),
    profileUrl: getProfileUrlBySlugOrId(post.userId),
    postUrl: getPostPageUrl(post),
    thumbnailUrl: post.thumbnailUrl,
    linkUrl: !!post.url ? getOutgoingUrl(post.url) : getPostPageUrl(post._id)
  };

  if(post.url)
    p.url = post.url;

  if(post.htmlBody)
    p.htmlBody = post.htmlBody;

  return p;
};

// default status for new posts
getDefaultPostStatus = function (user) {
  var hasAdminRights = typeof user === 'undefined' ? false : isAdmin(user);
  if (hasAdminRights || !getSetting('requirePostsApproval', false)) {
    // if user is admin, or else post approval is not required
    return STATUS_APPROVED
  } else {
    // else
    return STATUS_PENDING
  }
}

getPostPageUrl = function(post){
  return getSiteUrl()+'posts/'+post._id;
};

getPostEditUrl = function(id){
  return getSiteUrl()+'posts/'+id+'/edit';
};

// for a given post, return its link if it has one, or else its post page URL
getPostLink = function (post) {
  return !!post.url ? getOutgoingUrl(post.url) : getPostPageUrl(post);
};

checkForPostsWithSameUrl = function (url) {

  // check that there are no previous posts with the same link in the past 6 months
  var sixMonthsAgo = moment().subtract(6, 'months').toDate();
  var postWithSameLink = Posts.findOne({url: url, postedAt: {$gte: sixMonthsAgo}});

  if(typeof postWithSameLink !== 'undefined'){
    Meteor.call('upvotePost', postWithSameLink);
    // note: error.details returns undefined on the client, so add post ID to reason
    throw new Meteor.Error('603', i18n.t('this_link_has_already_been_posted') + '|' + postWithSameLink._id, postWithSameLink._id);
  }
}

// ------------------------------------------------------------------------------------------- //
// ------------------------------------------ Hooks ------------------------------------------ //
// ------------------------------------------------------------------------------------------- //

Posts.before.insert(function (userId, doc) {
  if(Meteor.isServer && !!doc.body)
    doc.htmlBody = sanitize(marked(doc.body));
});

Posts.before.update(function (userId, doc, fieldNames, modifier, options) {
  // if body is being modified, update htmlBody too
  if (Meteor.isServer && modifier.$set && modifier.$set.body) {
    modifier.$set = modifier.$set || {};
    modifier.$set.htmlBody = sanitize(marked(modifier.$set.body));
  }
});

// ------------------------------------------------------------------------------------------- //
// --------------------------------------- Submit Post --------------------------------------- //
// ------------------------------------------------------------------------------------------- //

submitPost = function (post) {
  console.log(post)
  var userId = post.userId, // at this stage, a userId is expected
      user = Meteor.users.findOne(userId);
  // ------------------------------ Checks ------------------------------ //

  // check that a title was provided
  if(!post.title)
    throw new Meteor.Error(602, i18n.t('please_fill_in_a_title'));

  // check that there are no posts with the same URL
  if(!!post.url)
    checkForPostsWithSameUrl(post.url);

  // ------------------------------ Properties ------------------------------ //

  defaultProperties = {
    createdAt: new Date(),
    author: getDisplayNameById(userId),
    upvotes: 0,
    downvotes: 0,
    commentCount: 0,
    clickCount: 0,
    viewCount: 0,
    baseScore: 0,
    score: 0,
    inactive: false,
    sticky: false,
    status: getDefaultPostStatus(),
    postedAt: new Date()
  };

  post = _.extend(defaultProperties, post);

  // clean up post title
  post.title = cleanUp(post.title);

  // ------------------------------ Callbacks ------------------------------ //

  // run all post submit server callbacks on post object successively
  post = postSubmitMethodCallbacks.reduce(function(result, currentFunction) {
      return currentFunction(result);
  }, post);

  // ------------------------------ Insert ------------------------------ //

  // console.log(post)
  post._id = Posts.insert(post);

  // ------------------------------ Callbacks ------------------------------ //

  // run all post submit server callbacks on post object successively
  post = postAfterSubmitMethodCallbacks.reduce(function(result, currentFunction) {
      return currentFunction(result);
  }, post);

  // ------------------------------ After Insert ------------------------------ //

  // increment posts count
  Meteor.users.update({_id: userId}, {$inc: {postCount: 1}});

  var postAuthor =  Meteor.users.findOne(userId);

  Meteor.call('upvotePost', post, postAuthor);
  console.log(post)
  return post;
}

// ------------------------------------------------------------------------------------------- //
// ----------------------------------------- Methods ----------------------------------------- //
// ------------------------------------------------------------------------------------------- //

postClicks = [];
postViews = [];

Meteor.methods({

  submitPost: function(post){

    // required properties:
    // title

    // optional properties
    // URL
    // body
    // categories
    // thumbnailUrl

    // NOTE: the current user and the post author user might be two different users!
    var user = Meteor.user(),
        hasAdminRights = isAdmin(user);

    // ------------------------------ Checks ------------------------------ //

    // check that user can post
    if (!user || !canPost(user))
      throw new Meteor.Error(601, i18n.t('you_need_to_login_or_be_invited_to_post_new_stories'));

    // --------------------------- Rate Limiting -------------------------- //

    var timeSinceLastPost=timeSinceLast(user, Posts),
        numberOfPostsInPast24Hours=numberOfItemsInPast24Hours(user, Posts),
        postInterval = Math.abs(parseInt(getSetting('postInterval', 30))),
        maxPostsPer24Hours = Math.abs(parseInt(getSetting('maxPostsPerDay', 30)));

    if(!hasAdminRights){
      // check that user waits more than X seconds between posts
      if(!this.isSimulation && timeSinceLastPost < postInterval)
        throw new Meteor.Error(604, i18n.t('please_wait')+(postInterval-timeSinceLastPost)+i18n.t('seconds_before_posting_again'));

      // check that the user doesn't post more than Y posts per day
      if(!this.isSimulation && numberOfPostsInPast24Hours > maxPostsPer24Hours)
        throw new Meteor.Error(605, i18n.t('sorry_you_cannot_submit_more_than')+maxPostsPer24Hours+i18n.t('posts_per_day'));
    }

    // ------------------------------ Properties ------------------------------ //

    // admin-only properties
    // status
    // postedAt
    // userId
    // sticky (default to false)

    // if user is not admin, clear restricted properties
    if (!hasAdminRights) {
      delete post.status;
      delete post.postedAt;
      delete post.userId;
      delete post.sticky;
    }

    // if no post status has been set, set it now
    if (!post.status) {
      post.status = getDefaultPostStatus(user);
    }

    // if no userId has been set, default to current user id
    if (!post.userId) {
      post.userId = user._id
    }

    return submitPost(post);
  },

  editPost: function (postId, updateObject) {

    var user = Meteor.user();

    // console.log(updateObject)

    // ------------------------------ Checks ------------------------------ //

    // check that user can edit
    if (!user || !canEdit(user, Posts.findOne(postId)))
      throw new Meteor.Error(601, i18n.t('sorry_you_cannot_edit_this_post'));

    // ------------------------------ Callbacks ------------------------------ //

    // run all post submit server callbacks on updateObject successively
    updateObject = postEditMethodCallbacks.reduce(function(result, currentFunction) {
        return currentFunction(result);
    }, updateObject);

    console.log(updateObject)

    // ------------------------------ Update ------------------------------ //

    Posts.update(postId, updateObject);

    // ------------------------------ Callbacks ------------------------------ //

    // run all post submit server callbacks on updateObject successively
    updateObject = postAfterEditMethodCallbacks.reduce(function(result, currentFunction) {
        return currentFunction(result);
    }, updateObject);

    // ------------------------------ After Update ------------------------------ //

    return Posts.findOne(postId);

  },

  setPostedAt: function(post, customPostedAt){

    var postedAt = new Date(); // default to current date and time

    if(isAdmin(Meteor.user()) && typeof customPostedAt !== 'undefined') // if user is admin and a custom datetime has been set
      postedAt = customPostedAt;

    Posts.update(post._id, {$set: {postedAt: postedAt}});
  },

  approvePost: function(post){
    if(isAdmin(Meteor.user())){
      var set = {status: 2};

      // unless post is already scheduled and has a postedAt date, set its postedAt date to now
      if (!post.postedAt)
        set.postedAt = new Date();

      var result = Posts.update(post._id, {$set: set}, {validate: false});
    }else{
      flashMessage('You need to be an admin to do that.', "error");
    }
  },

  unapprovePost: function(post){
    if(isAdmin(Meteor.user())){
      Posts.update(post._id, {$set: {status: 1}});
    }else{
      flashMessage('You need to be an admin to do that.', "error");
    }
  },

  increasePostViews: function(postId, sessionId){
    this.unblock();

    // only let users increment a post's view counter once per session
    var view = {_id: postId, userId: this.userId, sessionId: sessionId};

    if(_.where(postViews, view).length == 0){
        postViews.push(view);
        Posts.update(postId, { $inc: { viewCount: 1 }});
    }
  },

  increasePostClicks: function(postId, sessionId){
    this.unblock();

    // only let clients increment a post's click counter once per session
    var click = {_id: postId, userId: this.userId, sessionId: sessionId};

    if(_.where(postClicks, click).length == 0){
      postClicks.push(click);
      Posts.update(postId, { $inc: { clickCount: 1 }});
    }
  },

  deletePostById: function(postId) {
    // remove post comments
    // if(!this.isSimulation) {
    //   Comments.remove({post: postId});
    // }
    // NOTE: actually, keep comments after all

    var post = Posts.findOne({_id: postId});

    if(!Meteor.userId() || !canEditById(Meteor.userId(), post)) throw new Meteor.Error(606, 'You need permission to edit or delete a post');

    // decrement post count
    Meteor.users.update({_id: post.userId}, {$inc: {postCount: -1}});

    // delete post
    Posts.remove(postId);
  }

});
<|endoftext|>


<commit_before>/*
 * File: app/store/SelectPlotTypeStore.js
 *
 * This file was generated by Sencha Architect version 2.2.2.
 * http://www.sencha.com/products/architect/
 *
 * This file requires use of the Ext JS 4.2.x library, under independent license.
 * License of Sencha Architect does not include license for Ext JS 4.2.x. For more
 * details see http://www.sencha.com/license or contact license@sencha.com.
 *
 * This file will be auto-generated each and everytime you save your project.
 *
 * Do NOT hand edit this file.
 */

Ext.define('C.store.SelectPlotTypeStore', {
	extend: 'Ext.data.Store',

	constructor: function(cfg) {
		var me = this;
		cfg = cfg || {};
		me.callParent([Ext.apply({
			autoLoad: false,
			storeId: 'MyStore',
			data: [
				{
					plotTypeName: 'Total Consumption',
					plotTypeTag: 'all'
				},
				{
					plotTypeName: 'Installation Consumption',
					plotTypeTag: 'inst'
				},
				{
					plotTypeName: 'Person Consumption',
					plotTypeTag: 'pers'
				},
				{
					plotTypeName: 'Appliance Consumption',
					plotTypeTag: 'app'
				}
			],
			fields: [
				{
					name: 'plotTypeName',
					type: 'string'
				},
				{
					name: 'plotTypeTag',
					type: 'string'
				}
			]
		}, cfg)]);
	}
});<commit_msg>minor<commit_after><|endoftext|>


<commit_before>/* eslint global-require:0 */
import _ from 'underscore';
import ReactTestUtils from 'react-addons-test-utils';

import Config from '../../src/config'
import N1SpecLoader from './n1-spec-loader'
import TimeReporter from './time-reporter'
import N1GuiReporter from './n1-gui-reporter';
import jasmineExports from './jasmine';
import ConsoleReporter from './console-reporter'
import MasterAfterEach from './master-after-each'
import MasterBeforeEach from './master-before-each'
import nylasTestConstants from './nylas-test-constants'
import * as jasmineExtensions from './jasmine-extensions'
import * as reactTestUtilsExtensions from './react-test-utils-extensions'

class N1SpecRunner {
  runSpecs(loadSettings) {
    this.loadSettings = loadSettings
    this._extendGlobalWindow();
    this._setupJasmine();
    this._setupNylasEnv();
    this._setupWindow();
    Object.assign(ReactTestUtils, reactTestUtilsExtensions)
    MasterBeforeEach.setup(this.loadSettings, jasmineExports.beforeEach)
    MasterAfterEach.setup(this.loadSettings, jasmineExports.afterEach)
    N1SpecLoader.loadSpecs(this.loadSettings, this.jasmineEnv);
    this.jasmineEnv.execute();
  }

  /**
   * Put jasmine methods on the global scope so they can be used anywhere
   * without importing jasmine.
   */
  _extendGlobalWindow() {
    Object.assign(window, {
      jasmine: jasmineExports.jasmine,

      it: jasmineExports.it,
      xit: jasmineExports.xit,
      runs: jasmineExports.runs,
      waits: jasmineExports.waits,
      spyOn: jasmineExports.spyOn,
      expect: jasmineExports.expect,
      waitsFor: jasmineExports.waitsFor,
      describe: jasmineExports.describe,
      xdescribe: jasmineExports.xdescribe,
      afterEach: jasmineExports.afterEach,
      beforeEach: jasmineExports.beforeEach,
      testNowMoment: jasmineExtensions.testNowMoment,
      waitsForPromise: jasmineExtensions.waitsForPromise,
    }, nylasTestConstants)

    this.jasmineEnv = jasmineExports.jasmine.getEnv();
  }

  _setupJasmine() {
    this._addReporters()
    this._initializeDOM()
    this._extendJasmineMethods();

    // On load, this will require "jasmine-focused" which looks up the
    // global `jasmine` object and extends onto it:
    // fdescribe, ffdescribe, fffdescribe, fit, ffit, fffit
    require('jasmine-tagged');

    // On load this will extend jasmine's `beforeEach`
    require('jasmine-json');
  }

  _setupNylasEnv() {
    // We need to mock the config even before `beforeEach` runs because it
    // gets accessed on module definitions
    const fakePersistedConfig = {env: 'production'};
    NylasEnv.config = new Config();
    NylasEnv.config.settings = fakePersistedConfig;

    NylasEnv.restoreWindowDimensions();
    NylasEnv.themes.loadBaseStylesheets();
    NylasEnv.themes.requireStylesheet('../static/jasmine');
    NylasEnv.themes.initialLoadComplete = true;
    NylasEnv.keymaps.loadKeymaps();
  }

  _setupWindow() {
    window.addEventListener('core:close', () => window.close());
    window.addEventListener('beforeunload', () => {
      NylasEnv.storeWindowDimensions();
      return NylasEnv.saveSync();
    });

    // On load this will extend the window object
    require('../../src/window');
  }

  _addReporters() {
    const timeReporter = new TimeReporter();
    const consoleReporter = new ConsoleReporter();

    // This needs to be `required` at runtime because terminal-reporter
    // depends on jasmine-tagged, which depends on jasmine-focused, which
    // on require will attempt to extend the `jasmine` object with
    // methods. The `jasmine` object has to be attached to the global
    // scope before it gets extended. This is done in
    // `_extendGlobalWindow`.
    const N1TerminalReporter = require('./terminal-reporter').default

    const terminalReporter = new N1TerminalReporter();

    if (NylasEnv.getLoadSettings().showSpecsInWindow) {
      this.jasmineEnv.addReporter(N1GuiReporter);
    } else {
      this.jasmineEnv.addReporter(terminalReporter);
    }
    this.jasmineEnv.addReporter(timeReporter);
    this.jasmineEnv.addReporter(consoleReporter);
  }

  _initializeDOM() {
    const div = document.createElement('div');
    div.id = 'jasmine-content';
    document.body.appendChild(div);
    document.querySelector('html').style.overflow = 'initial';
    document.querySelector('body').style.overflow = 'initial';
  }

  _extendJasmineMethods() {
    const jasmine = jasmineExports.jasmine;

    jasmine.getEnv().defaultTimeoutInterval = 250;

    // Use underscore's definition of equality for toEqual assertions
    jasmine.getEnv().addEqualityTester(_.isEqual);

    jasmine.unspy = jasmineExtensions.unspy
    jasmine.attachToDOM = jasmineExtensions.attachToDOM

    const origEmitObject = jasmine.StringPrettyPrinter.prototype.emitObject;
    jasmine.StringPrettyPrinter.prototype.emitObject = function emitObject(obj) {
      if (obj.inspect) {
        return this.append(obj.inspect());
      }
      return origEmitObject.call(this, obj);
    };
  }
}
export default new N1SpecRunner()
<commit_msg>fix(spec): let ./N1.sh --test=window work<commit_after>/* eslint global-require:0 */
import _ from 'underscore';
import ReactTestUtils from 'react-addons-test-utils';

import Config from '../../src/config'
import N1SpecLoader from './n1-spec-loader'
import TimeReporter from './time-reporter'
import N1GuiReporter from './n1-gui-reporter';
import jasmineExports from './jasmine';
import ConsoleReporter from './console-reporter'
import MasterAfterEach from './master-after-each'
import MasterBeforeEach from './master-before-each'
import nylasTestConstants from './nylas-test-constants'
import * as jasmineExtensions from './jasmine-extensions'
import * as reactTestUtilsExtensions from './react-test-utils-extensions'

class N1SpecRunner {
  runSpecs(loadSettings) {
    this.loadSettings = loadSettings
    this._extendGlobalWindow();
    this._setupJasmine();
    this._setupNylasEnv();
    this._setupWindow();
    Object.assign(ReactTestUtils, reactTestUtilsExtensions)
    MasterBeforeEach.setup(this.loadSettings, jasmineExports.beforeEach)
    MasterAfterEach.setup(this.loadSettings, jasmineExports.afterEach)
    N1SpecLoader.loadSpecs(this.loadSettings, this.jasmineEnv);
    this.jasmineEnv.execute();
  }

  /**
   * Put jasmine methods on the global scope so they can be used anywhere
   * without importing jasmine.
   */
  _extendGlobalWindow() {
    Object.assign(window, {
      jasmine: jasmineExports.jasmine,

      it: jasmineExports.it,
      xit: jasmineExports.xit,
      runs: jasmineExports.runs,
      waits: jasmineExports.waits,
      spyOn: jasmineExports.spyOn,
      expect: jasmineExports.expect,
      waitsFor: jasmineExports.waitsFor,
      describe: jasmineExports.describe,
      xdescribe: jasmineExports.xdescribe,
      afterEach: jasmineExports.afterEach,
      beforeEach: jasmineExports.beforeEach,
      testNowMoment: jasmineExtensions.testNowMoment,
      waitsForPromise: jasmineExtensions.waitsForPromise,
    }, nylasTestConstants)

    this.jasmineEnv = jasmineExports.jasmine.getEnv();
  }

  _setupJasmine() {
    this._addReporters()
    this._initializeDOM()
    this._extendJasmineMethods();

    // On load, this will require "jasmine-focused" which looks up the
    // global `jasmine` object and extends onto it:
    // fdescribe, ffdescribe, fffdescribe, fit, ffit, fffit
    require('jasmine-tagged');

    // On load this will extend jasmine's `beforeEach`
    require('jasmine-json');
  }

  _setupNylasEnv() {
    // We need to mock the config even before `beforeEach` runs because it
    // gets accessed on module definitions
    const fakePersistedConfig = {env: 'production'};
    NylasEnv.config = new Config();
    NylasEnv.config.settings = fakePersistedConfig;

    NylasEnv.restoreWindowDimensions();
    NylasEnv.themes.loadBaseStylesheets();
    NylasEnv.themes.requireStylesheet('../../static/jasmine');
    NylasEnv.themes.initialLoadComplete = true;
    NylasEnv.keymaps.loadKeymaps();
  }

  _setupWindow() {
    window.addEventListener('core:close', () => window.close());
    window.addEventListener('beforeunload', () => {
      NylasEnv.storeWindowDimensions();
      return NylasEnv.saveSync();
    });

    // On load this will extend the window object
    require('../../src/window');
  }

  _addReporters() {
    const timeReporter = new TimeReporter();
    const consoleReporter = new ConsoleReporter();

    // This needs to be `required` at runtime because terminal-reporter
    // depends on jasmine-tagged, which depends on jasmine-focused, which
    // on require will attempt to extend the `jasmine` object with
    // methods. The `jasmine` object has to be attached to the global
    // scope before it gets extended. This is done in
    // `_extendGlobalWindow`.
    const N1TerminalReporter = require('./terminal-reporter').default

    const terminalReporter = new N1TerminalReporter();

    if (NylasEnv.getLoadSettings().showSpecsInWindow) {
      this.jasmineEnv.addReporter(N1GuiReporter);
    } else {
      this.jasmineEnv.addReporter(terminalReporter);
    }
    this.jasmineEnv.addReporter(timeReporter);
    this.jasmineEnv.addReporter(consoleReporter);
  }

  _initializeDOM() {
    const div = document.createElement('div');
    div.id = 'jasmine-content';
    document.body.appendChild(div);
    document.querySelector('html').style.overflow = 'initial';
    document.querySelector('body').style.overflow = 'initial';

    if (NylasEnv.getLoadSettings().showSpecsInWindow) {
      const el = document.querySelector("#application-loading-cover");
      el.parentNode.removeChild(el);
      NylasEnv.show();
    }
  }

  _extendJasmineMethods() {
    const jasmine = jasmineExports.jasmine;

    jasmine.getEnv().defaultTimeoutInterval = 250;

    // Use underscore's definition of equality for toEqual assertions
    jasmine.getEnv().addEqualityTester(_.isEqual);

    jasmine.unspy = jasmineExtensions.unspy
    jasmine.attachToDOM = jasmineExtensions.attachToDOM

    const origEmitObject = jasmine.StringPrettyPrinter.prototype.emitObject;
    jasmine.StringPrettyPrinter.prototype.emitObject = function emitObject(obj) {
      if (obj.inspect) {
        return this.append(obj.inspect());
      }
      return origEmitObject.call(this, obj);
    };
  }
}
export default new N1SpecRunner()
<|endoftext|>


<commit_before>/*
localStorage should store:
1. whether user has submitted evaluation (get started) form
2. user's name and panic image choice
3. results of eval form (recommended starting point)
4. whether user has begun exercises
5. image viewed when user last left page
6. previous visit dates and respective "level reached" numbers (for chart)

*/

//OBJECT THAT WILL BE STORED IN LOCALSTORAGE AND WILL TRACK USER DETAILS/PROGRESS
var userInfo {
  evalComplete: false;
  userName: '';
  panicImage: '';
  recommendedStartImage: '';
  exercisesBegun: false;
  lastViewedImage: '';
  previousVisitData: [];
}
<commit_msg>Corrected userInfo object syntax<commit_after>/*
localStorage should store:
1. whether user has submitted evaluation (get started) form
2. user's name and panic image choice
3. results of eval form (recommended starting point)
4. whether user has begun exercises
5. image viewed when user last left page
6. previous visit dates and respective "level reached" numbers (for chart)

*/

//OBJECT THAT WILL BE STORED IN LOCALSTORAGE AND WILL TRACK USER DETAILS/PROGRESS
var userInfo = {
  evalComplete: false,
  userName: '',
  panicImage: '',
  recommendedStartImage: '',
  exercisesBegun: false,
  lastViewedImage: '',
  previousVisitData: []
}
<|endoftext|>


<commit_before>(function(queryFactory, $, undefined) {
	// Class QueryFactory
	// Purpose: Create a form which can yield an influxDB query
	// Includes:
	//

	////////////
	// Public:
	////////////

	//////////////////////////
	// Initialize queryFactory
	// This should be all you need to get queryFactory working on your page
	// appendToElem = element to append the queryFactoryContainer to
	// _buttonText = text to display on the query button
	// _queryCallback = function that accepts an InfluxDB Query Language query string
	//////////////////////////
	queryFactory.init = function(appendToElem, _buttonText, _queryCallback) {
		buttonText = _buttonText;
		queryCallback = _queryCallback;

		$(appendToElem).append("

");
		makeSeriesSelection();
		makeMeasurementSelection();
		makeWHEREClauseButton();
		$('#queryFactory').append('
');
		makeLimitTextbox();
		$('#queryFactory').append('
');
		makeQueryButton();
		makeLabel();

		eventRegisterENTERKey();
		eventRegisterWHEREClause();
	}

	//Build a query string from the components of the queryFactory form
	//Returns a proper InfluxDB Query Language query.
	queryFactory.formToQueryString = function() {
		var queryStr = "SELECT ";
		queryStr += $('#measurement').val() + " FROM ";
		queryStr += $('#series').val();
		var dateStrArr = formatDateTextboxes();
		var timeStrArr = formatTimeTextboxes();
		var beforeOrAfterArr = formatBeforeOrAfterSelections();
		var booleansArr = formatBooleanSelections();
		if (dateStrArr.length > 0) {
			queryStr += " WHERE ";
			for (var index = 0; index < dateStrArr.length; ++index) {
				if (index > 0) {
					queryStr += " " + booleansArr[index - 1] + " ";
				}
				var timestampStr = "'" + dateStrArr[index] + " " + timeStrArr[index] + "'";

				queryStr += "time" + beforeOrAfterArr[index];
				queryStr += timestampStr;
			}
		}
		var limitStr = $('#limitTextbox').val();
		if (limitStr > 0)
			queryStr += " LIMIT " + limitStr;

		console.log("Query Factory String: " + queryStr);
		return queryStr;
	}
	////////////////////////
	// Button Click Handlers
	////////////////////////
	queryFactory.resetButtonOnClick = function() {
		$('.beforeOrAfter').remove();
		$('.datePicker').remove();
		$('.timePicker').remove();
		$('.booleanChoice').remove();
		while (WHERECount > 0) {
			$('#queryFactory br:nth-last-of-type(3)').remove();
			--WHERECount;
		}
		$('#resetBtn').remove();
	}
	queryFactory.WHEREClauseButtonOnClick = function() {
		$('#queryBtn').remove();
		$('#resetBtn').remove();
		$('#queryFactory br:last-of-type').remove();
		$('#queryFactory br:last-of-type').remove();
		queryLimit = $('#limitTextbox').val();
		$('#queryFactory p:last-of-type').remove();
		$('#queryFactory a').remove();
		if ($('#queryFactory br').length > 0) {
			makeBooleanChoiceSelection();
		}
		++WHERECount;

		$('#queryFactory').append('
');
		makeBeforeOrAfterSelection();
		makeDateEntryTextbox();
		makeTimeEntryTextbox();
		makeWHEREClauseButton();
		$('#queryFactory').append('
');
		makeLimitTextbox();
		$('#queryFactory').append('
');
		makeQueryButton();
		makeResetButton();
		eventRegisterWHEREClause();
		$('#queryFactory a').focus();

	}
	queryFactory.QueryButtonOnClick = function() {
		queryCallback(queryFactory.formToQueryString())
	}
	///////////
	// Private:
	///////////
	var WHERECount = 0;	//Number of WHERE clauses
	var buttonText = null;	//Text to print on the "Query" buton
	var queryCallback = null;	// Callback to pass the query created by queryFactory into
	var queryLimit = 1000;		// Keep track of query limit

	// Perform a query to the database and run callback functino on the data
	function doQueryCallback(db, query, callback) {
		//console.log(query);
		var queryURL = "http://weatherspot.us/db/query.php?db=" + db + "&query=" + encodeURIComponent(query);
		$.ajax({
			type: 'GET',
			url: queryURL,
			success: function(data) {
				callback(data);
			},
			error: function(jqXHR, textStatus, errorThrown) {
			},
			dataType: "json"
		});
	}
	////////////////////
	// The following is a set of functions creating pieces of the form one at a
	// time, they are all called by the init() function or caused by button clicks.
	////////////////////
	function makeSeriesSelection() {
		doQueryCallback("weather", "LIST SERIES;", function(data) {
			var appendStr = "<select id='series'>";
			$.each(data[0].points, function(i, e) {
				appendStr += "<option value='" + e[1] + "'>" + e[1] + "</option>";
			});
			appendStr += "<option value='/.*/'>All</option>";
			appendStr += "</select>";
			$('#queryFactory').prepend(appendStr);
		});
	}
	function makeMeasurementSelection() {
		var appendStr = "<select id='measurement'>";
		appendStr += "<option value='temperature'>Temperature</option>";
		appendStr += "<option value='humidity'>Humidity</option>";
		appendStr += "<option value='pressure'>Pressure</option>";
		appendStr += "<option value='lighting'>Lighting</option>";
		appendStr += "<option value='*'>All</option>";
		appendStr += "</select>";
		$('#queryFactory').append(appendStr);
	}
	function makeBeforeOrAfterSelection() {
		var appendStr = "<select class='beforeOrAfter'>";
		appendStr += "<option value='<'>Before</option>";
		appendStr += "<option value='>'>After</option>";
		$('#queryFactory').append(appendStr);
	}
	function makeDateEntryTextbox() {
		var dateStr = "";
		if (WHERECount == 1) {
			$('.beforeOrAfter:last-of-type option:nth-child(2)').prop('selected', true);
			dateStr = todaysDateString(-1);				//Todays date minus 1 day
		}
		else if (WHERECount == 2)
			dateStr = todaysDateString();
		else
			dateStr = "Date...";

		//console.log(dateStr);
		var appendStr = "<input value='" + dateStr + "' type='text' class='datePicker' style='width: 10em'></input>";
		$('#queryFactory').append(appendStr);
		$('.datePicker').datepicker( { dateFormat: "yy-mm-dd" } );
	}
	function makeTimeEntryTextbox() {
		var timeStr = "";
		if (WHERECount == 1) {
			timeStr = nowTimeString();
		}
		else if (WHERECount == 2)
			timeStr = nowTimeString();
		else
			timeStr = "Time...";
		var appendStr = "<input value='" + timeStr + "' type='text' class='timePicker' style='width: 7em'></input>";
		$('#queryFactory').append(appendStr);
		$('.timePicker').timepicker( { 'timeFormat': 'H:i:s' } );
	}
	function makeBooleanChoiceSelection() {
		var appendStr = "<select class='booleanChoice'>";
		appendStr += "<option value='and'>And</option>";
		appendStr += "<option value='or'>Or</option>";
		$('#queryFactory').append(appendStr);
	}
	function makeResetButton() {
		$('#queryFactory').append("<button id='resetBtn' type='button' onclick='queryFactory.resetButtonOnClick()'>Reset</button>");
	}
	function makeWHEREClauseButton() {
		var appendStr = "Add Where Clause"
		$('#queryFactory').append(appendStr);
	}
	function makeQueryButton() {
		$('#queryFactory').append("<button id='queryBtn' type='button' onclick='queryFactory.QueryButtonOnClick()'>" + buttonText + "</button>");
	}
	function makeLabel(label) {
		label = label || "All Times are UTC";	// Label defaults to "All Times are UTC"
		var prependStr = "
" + label + "
";
		$('#factoryContainer').prepend(prependStr);
	}
	function makeLimitTextbox() {
		var appendStr = "
Query Limit:<input value='" + (''+queryLimit) + "' type='number' id='limitTextbox' style='width: 6em'></input>
";
		$('#queryFactory').append(appendStr);
	}
	//////////////////////
	// Time to Strig related functions
	//////////////////////
	function nowTimeString() {
		var d = new Date();
		var hours = d.getHours() + getTimeZoneAdjustment("EDT");
		var minutes = d.getMinutes();
		var seconds = d.getSeconds();
		var output = ((''+hours).length<2 ? '0' : '') + hours + ':' +
		    ((''+minutes).length<2 ? '0' : '') + minutes + ':' +
		    ((''+seconds).length<2 ? '0' : '') + seconds;

		return output;
	}
	function getTimeZoneAdjustment(zone) {
		//Add more timezones!
		switch(zone) {
			case "EDT":	//Eastern Daylight Time UTC-4
				return 4;
			default:
				return 0;
		}
	}
	function todaysDateString(dayOffset) {
		dayOffset = dayOffset || 0;	// Default parameter to 0
		// Thanks Tadeck@stackoverflow
		// http://stackoverflow.com/questions/8398897/how-to-get-current-date-in-jquery
		var d = new Date();
		var month = d.getMonth()+1;
		var day = d.getDate()+dayOffset;

		var output = d.getFullYear() + '-' +
		    ((''+month).length<2 ? '0' : '') + month + '-' +
		    ((''+day).length<2 ? '0' : '') + day;

		return output;

	}
	///////////////////
	// Following are functions for formatting the textboxes used to form WHERE clauses
	// They each return an array of strings containing the values of their respective
	// textboxes
	///////////////////
	function formatDateTextboxes() {
		if ($('.datePicker').length == 0)
			return "";

		var formatStrArr = [];
		$('.datePicker').each(function(index, elem) {
			formatStrArr[index] = $(elem).val();
			//console.log("DATEBOX Index: " + index + " Element: " + $(elem).val());
		});
		return formatStrArr;
	}
	function formatTimeTextboxes() {
		var formatStrArr = [];
		$('.timePicker').each(function(index, elem) {
			var val = $(elem).val()
			if (val == "Time...")
				val = "00:00:00";
			formatStrArr[index] = val;
			//console.log("TIMEBOX Index: " + index + " Element: " + val);
		});
		return formatStrArr;
	}
	function formatBeforeOrAfterSelections() {
		var formatStrArr = []
		$('.beforeOrAfter').each(function(index, elem) {
			formatStrArr[index] = $(elem).val();
			//console.log("BEFOAFT Index: " + index + " Element: " + $(elem).val());
		});
		return formatStrArr;
	}
	function formatBooleanSelections() {
		var formatStrArr = []
		$('.booleanChoice').each(function(index, elem) {
			formatStrArr[index] = $(elem).val();
			//console.log("BOOLEAN Index: " + index + " Element: " + $(elem).val());
		});
		return formatStrArr;
	}
	///////////////
	// Event Listener Functions
	///////////////
	function eventRegisterENTERKey() {
		$("#queryFactory").bind('keydown', function(e) {
			if (e.keyCode == 13) {
				queryFactory.QueryButtonOnClick();
				console.log("FakeClicked");
				if ($(this).has("select"))
				{
					console.log("Enter on Select");
					return false;
				}
			}
			return true;
		});
	}
	function eventRegisterWHEREClause() {
		$("#queryFactory a").bind('keydown', function(e) {
			if (e.keyCode == 13) {
				queryFactory.WHEREClauseButtonOnClick();
				console.log("FakeClicked");
				if ($(this).has("select"))
				{
					console.log("Enter on Select");
					return false;
				}
			}
			return true;
		});
	}
}( window.queryFactory = window.queryFactory || {}, jQuery));
<commit_msg>Chained removes together to improve performance, fixed 'Reset' button handling<commit_after>(function(queryFactory, $, undefined) {
	// Class QueryFactory
	// Purpose: Create a form which can yield an influxDB query
	// Includes:
	//

	////////////
	// Public:
	////////////

	//////////////////////////
	// Initialize queryFactory
	// This should be all you need to get queryFactory working on your page
	// appendToElem = element to append the queryFactoryContainer to
	// _buttonText = text to display on the query button
	// _queryCallback = function that accepts an InfluxDB Query Language query string
	//////////////////////////
	queryFactory.init = function(appendToElem, _buttonText, _queryCallback) {
		buttonText = _buttonText;
		queryCallback = _queryCallback;

		$(appendToElem).append("

");
		makeSeriesSelection();
		makeMeasurementSelection();
		makeWHEREClauseButton();
		$('#queryFactory').append('
');
		makeLimitTextbox();
		$('#queryFactory').append('
');
		makeQueryButton();
		makeLabel();

		eventRegisterENTERKey();
		eventRegisterWHEREClause();
	}

	//Build a query string from the components of the queryFactory form
	//Returns a proper InfluxDB Query Language query.
	queryFactory.formToQueryString = function() {
		var queryStr = "SELECT ";
		queryStr += $('#measurement').val() + " FROM ";
		queryStr += $('#series').val();
		var dateStrArr = formatDateTextboxes();
		var timeStrArr = formatTimeTextboxes();
		var beforeOrAfterArr = formatBeforeOrAfterSelections();
		var booleansArr = formatBooleanSelections();
		if (dateStrArr.length > 0) {
			queryStr += " WHERE ";
			for (var index = 0; index < dateStrArr.length; ++index) {
				if (index > 0) {
					queryStr += " " + booleansArr[index - 1] + " ";
				}
				var timestampStr = "'" + dateStrArr[index] + " " + timeStrArr[index] + "'";

				queryStr += "time" + beforeOrAfterArr[index];
				queryStr += timestampStr;
			}
		}
		var limitStr = $('#limitTextbox').val();
		if (limitStr > 0)
			queryStr += " LIMIT " + limitStr;

		console.log("Query Factory String: " + queryStr);
		return queryStr;
	}
	////////////////////////
	// Button Click Handlers
	////////////////////////
	queryFactory.resetButtonOnClick = function() {
		$('.beforeOrAfter,.datePicker,.timePicker,.booleanChoice,#resetBtn').remove();
		while (WHERECount > 0) {
			$('#queryFactory br:nth-last-of-type(3)').remove();
			--WHERECount;
		}
	}
	queryFactory.WHEREClauseButtonOnClick = function() {
		queryLimit = $('#limitTextbox').val();
		$('#queryBtn,#resetBtn,#queryFactory br:last-of-type').remove();
		$('#queryFactory br:last-of-type,#queryFactory p:last-of-type,#queryFactory a').remove();
		if ($('#queryFactory br').length > 0) {
			makeBooleanChoiceSelection();
		}
		++WHERECount;

		$('#queryFactory').append('
');
		makeBeforeOrAfterSelection();
		makeDateEntryTextbox();
		makeTimeEntryTextbox();
		makeWHEREClauseButton();
		$('#queryFactory').append('
');
		makeLimitTextbox();
		$('#queryFactory').append('
');
		makeQueryButton();
		makeResetButton();
		eventRegisterWHEREClause();
		eventRegisterResetButton();
		$('#queryFactory a').focus();

	}
	queryFactory.QueryButtonOnClick = function() {
		queryCallback(queryFactory.formToQueryString())
	}
	///////////
	// Private:
	///////////
	var WHERECount = 0;	//Number of WHERE clauses
	var buttonText = null;	//Text to print on the "Query" buton
	var queryCallback = null;	// Callback to pass the query created by queryFactory into
	var queryLimit = 1000;		// Keep track of query limit

	// Perform a query to the database and run callback functino on the data
	function doQueryCallback(db, query, callback) {
		//console.log(query);
		var queryURL = "http://weatherspot.us/db/query.php?db=" + db + "&query=" + encodeURIComponent(query);
		$.ajax({
			type: 'GET',
			url: queryURL,
			success: function(data) {
				callback(data);
			},
			error: function(jqXHR, textStatus, errorThrown) {
			},
			dataType: "json"
		});
	}
	////////////////////
	// The following is a set of functions creating pieces of the form one at a
	// time, they are all called by the init() function or caused by button clicks.
	////////////////////
	function makeSeriesSelection() {
		doQueryCallback("weather", "LIST SERIES;", function(data) {
			var appendStr = "<select id='series'>";
			$.each(data[0].points, function(i, e) {
				appendStr += "<option value='" + e[1] + "'>" + e[1] + "</option>";
			});
			appendStr += "<option value='/.*/'>All</option>";
			appendStr += "</select>";
			$('#queryFactory').prepend(appendStr);
		});
	}
	function makeMeasurementSelection() {
		var appendStr = "<select id='measurement'>";
		appendStr += "<option value='temperature'>Temperature</option>";
		appendStr += "<option value='humidity'>Humidity</option>";
		appendStr += "<option value='pressure'>Pressure</option>";
		appendStr += "<option value='lighting'>Lighting</option>";
		appendStr += "<option value='*'>All</option>";
		appendStr += "</select>";
		$('#queryFactory').append(appendStr);
	}
	function makeBeforeOrAfterSelection() {
		var appendStr = "<select class='beforeOrAfter'>";
		appendStr += "<option value='<'>Before</option>";
		appendStr += "<option value='>'>After</option>";
		$('#queryFactory').append(appendStr);
	}
	function makeDateEntryTextbox() {
		var dateStr = "";
		if (WHERECount == 1) {
			$('.beforeOrAfter:last-of-type option:nth-child(2)').prop('selected', true);
			dateStr = todaysDateString(-1);				//Todays date minus 1 day
		}
		else if (WHERECount == 2)
			dateStr = todaysDateString();
		else
			dateStr = "Date...";

		//console.log(dateStr);
		var appendStr = "<input value='" + dateStr + "' type='text' class='datePicker' style='width: 10em'></input>";
		$('#queryFactory').append(appendStr);
		$('.datePicker').datepicker( { dateFormat: "yy-mm-dd" } );
	}
	function makeTimeEntryTextbox() {
		var timeStr = "";
		if (WHERECount == 1) {
			timeStr = nowTimeString();
		}
		else if (WHERECount == 2)
			timeStr = nowTimeString();
		else
			timeStr = "Time...";
		var appendStr = "<input value='" + timeStr + "' type='text' class='timePicker' style='width: 7em'></input>";
		$('#queryFactory').append(appendStr);
		$('.timePicker').timepicker( { 'timeFormat': 'H:i:s' } );
	}
	function makeBooleanChoiceSelection() {
		var appendStr = "<select class='booleanChoice'>";
		appendStr += "<option value='and'>And</option>";
		appendStr += "<option value='or'>Or</option>";
		$('#queryFactory').append(appendStr);
	}
	function makeResetButton() {
		$('#queryFactory').append("<button id='resetBtn' type='button' onclick='queryFactory.resetButtonOnClick()'>Reset</button>");
	}
	function makeWHEREClauseButton() {
		var appendStr = "Add Where Clause"
		$('#queryFactory').append(appendStr);
	}
	function makeQueryButton() {
		$('#queryFactory').append("<button id='queryBtn' type='button' onclick='queryFactory.QueryButtonOnClick()'>" + buttonText + "</button>");
	}
	function makeLabel(label) {
		label = label || "All Times are UTC";	// Label defaults to "All Times are UTC"
		var prependStr = "
" + label + "
";
		$('#factoryContainer').prepend(prependStr);
	}
	function makeLimitTextbox() {
		var appendStr = "
Query Limit:<input value='" + (''+queryLimit) + "' type='number' id='limitTextbox' style='width: 6em'></input>
";
		$('#queryFactory').append(appendStr);
	}
	//////////////////////
	// Time to Strig related functions
	//////////////////////
	function nowTimeString() {
		var d = new Date();
		var hours = d.getHours() + getTimeZoneAdjustment("EDT");
		var minutes = d.getMinutes();
		var seconds = d.getSeconds();
		var output = ((''+hours).length<2 ? '0' : '') + hours + ':' +
		    ((''+minutes).length<2 ? '0' : '') + minutes + ':' +
		    ((''+seconds).length<2 ? '0' : '') + seconds;

		return output;
	}
	function getTimeZoneAdjustment(zone) {
		//Add more timezones!
		switch(zone) {
			case "EDT":	//Eastern Daylight Time UTC-4
				return 4;
			default:
				return 0;
		}
	}
	function todaysDateString(dayOffset) {
		dayOffset = dayOffset || 0;	// Default parameter to 0
		// Thanks Tadeck@stackoverflow
		// http://stackoverflow.com/questions/8398897/how-to-get-current-date-in-jquery
		var d = new Date();
		var month = d.getMonth()+1;
		var day = d.getDate()+dayOffset;

		var output = d.getFullYear() + '-' +
		    ((''+month).length<2 ? '0' : '') + month + '-' +
		    ((''+day).length<2 ? '0' : '') + day;

		return output;

	}
	///////////////////
	// Following are functions for formatting the textboxes used to form WHERE clauses
	// They each return an array of strings containing the values of their respective
	// textboxes
	///////////////////
	function formatDateTextboxes() {
		if ($('.datePicker').length == 0)
			return "";

		var formatStrArr = [];
		$('.datePicker').each(function(index, elem) {
			formatStrArr[index] = $(elem).val();
			//console.log("DATEBOX Index: " + index + " Element: " + $(elem).val());
		});
		return formatStrArr;
	}
	function formatTimeTextboxes() {
		var formatStrArr = [];
		$('.timePicker').each(function(index, elem) {
			var val = $(elem).val()
			if (val == "Time...")
				val = "00:00:00";
			formatStrArr[index] = val;
			//console.log("TIMEBOX Index: " + index + " Element: " + val);
		});
		return formatStrArr;
	}
	function formatBeforeOrAfterSelections() {
		var formatStrArr = []
		$('.beforeOrAfter').each(function(index, elem) {
			formatStrArr[index] = $(elem).val();
			//console.log("BEFOAFT Index: " + index + " Element: " + $(elem).val());
		});
		return formatStrArr;
	}
	function formatBooleanSelections() {
		var formatStrArr = []
		$('.booleanChoice').each(function(index, elem) {
			formatStrArr[index] = $(elem).val();
			//console.log("BOOLEAN Index: " + index + " Element: " + $(elem).val());
		});
		return formatStrArr;
	}
	///////////////
	// Event Listener Functions
	///////////////
	function eventRegisterENTERKey() {
		$("#queryFactory").bind('keydown', function(e) {
			if (e.keyCode == 13) {
				queryFactory.QueryButtonOnClick();
				console.log("FakeClicked");
				if ($(this).has("select"))
				{
					console.log("Enter on Select");
					return false;
				}
			}
			return true;
		});
	}
	function eventRegisterWHEREClause() {
		$("#queryFactory a").bind('keydown', function(e) {
			if (e.keyCode == 13) {
				queryFactory.WHEREClauseButtonOnClick();
				return false;
			}
			return true;
		});
	}
	function eventRegisterResetButton() {
		$("#resetBtn").bind('keydown', function(e) {
			if (e.keyCode == 13) {
				queryFactory.resetButtonOnClick()
				return false;
			}
			return true;
		});
	}
}( window.queryFactory = window.queryFactory || {}, jQuery));
<|endoftext|>


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

this.Preferences = SeleniumIDE.Preferences;

function saveOptions() {
  var options = this.options;
  var name;
  for (name in options) {
    var e = document.getElementById(name);
    if (e != null) {
      options[name] = e.checked != undefined ? e.checked.toString() : e.value;
    }
  }
  if (options.enableExperimentalFeatures.toLowerCase() != 'true' && options.selectedFormat != 'default') {
    if (!confirm(Message("options.confirmFormatDisable"))) {
      return false;
    }
  }
  updateFormatOptions();
  options['locatorBuildersOrder'] = this.locatorBuilderList.getListItems().join(',');
  this.pluginManager.updatePlugins(this.plugins);
  this.pluginManager.save(options);
  SeleniumIDE.Loader.getEditors().forEach(function(editor) {
    editor.app.setOptions(options);
  });
  Preferences.save(options);
  return true;
}

function loadFromOptions(options) {
  var key;
  for (key in options) {
    var e = document.getElementById(key);
    if (e != null) {
      if (e.checked != undefined) {
        e.checked = options[key] == 'true';

        //initialize the reload-button state
        if (key == "showDeveloperTools") {
          updateReloadButton(e.checked);
        }
      } else {
        e.value = options[key];
      }
    }
  }
}

function loadDefaultOptions() {
  if (confirm(Message("options.confirmLoadDefaultOptions"))) {
    loadFromOptions(Preferences.DEFAULT_OPTIONS);
    for (key in this.options) {
      if (/^formats\./.test(key)) {
        delete this.options[key];
      }
    }
    if (this.format) {
      showFormatDialog();
    }
    if (this.locatorBuilderList) {
      //By design the new locators will be temporarily lost. The missing locators will be automatically added when the options are saved
      this.locatorBuilderList.reload(options['locatorBuildersOrder'].split(','));
    }
    //TODO set plugin defaults
  }
}

function getPluginManager(options) {
  var editor = SeleniumIDE.Loader.getTopEditor();
  if (editor && editor.pluginManager) {
    return editor.pluginManager;
  }
  return new PluginManager(options);
}

function loadOptions() {
  var options = Preferences.load();
  this.options = options;
  loadFromOptions(options);
  this.pluginManager = getPluginManager(options);
  this.formats = new FormatCollection(options, this.pluginManager);

  loadFormatList();
  //Samit: Enh: Selected the options of the current format if available
  if (this.formats.findFormat(options.selectedFormat)) {
    selectFormat(options.selectedFormat);
  } else {
    selectFormat('default');
  }

  //Convert our normal listbox into a drag and drop reordered listbox
  this.locatorBuilderList = new DnDReorderedListbox(window.document.getElementById('locatorBuilder-list'), options['locatorBuildersOrder'].split(','));
  this.pluginErrors = this.pluginManager.errors;
  this.plugins = this.pluginManager.getPluginCollection();
//  this.plugins.callWhenReady(loadPluginList, this); //Samit: Enh: Work with async Addon Manager for Firefox 4
  loadPluginsWhenReady(this.plugins, this); //For some reason the above call stopped working, my guess is it is due to a different window context
}

function loadPluginsWhenReady(plugins, win) {
  if (!plugins.isReady()) {
    setTimeout(function() {
      loadPluginsWhenReady(plugins, win);
    }, 100);
  } else {
    win.loadPluginList();
  }
}

function loadFormatList() {
  var list = getClearedList("format-list");
  for (var j = 0; j < this.formats.formats.length; j++) {
    var format = this.formats.formats[j];
    list.appendItem(format.name, format.id);
  }
}

var encodingTestConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);

function chooseFile(target) {
  var nsIFilePicker = Components.interfaces.nsIFilePicker;
  var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
  fp.init(window, "Select extensions file", nsIFilePicker.modeOpen);
  fp.appendFilters(nsIFilePicker.filterAll);
  var res = fp.show();
  if (res == nsIFilePicker.returnOK) {
    var e = document.getElementById(target);
    if (e.value) {
      e.value += ', ' + fp.file.path;
    } else {
      e.value = fp.file.path;
    }
  }
}

function updateFormatOptions() {
  if (this.format && this.format.options) {
    for (key in this.format.options) {
      var e = document.getElementById("options_" + key);
      if (e) {
        if (e.checked != undefined) {
          this.options["formats." + this.formatInfo.id + "." + key] = e.checked.toString();
        } else {
          this.options["formats." + this.formatInfo.id + "." + key] = e.value;
        }
      }
    }
  }
}

function selectFormat(id) {
  document.getElementById("format-list").value = id;
  updateFormatSelection();
}

function updateFormatSelection() {
  this.updateFormatOptions();
  this.showFormatDialog();
}

function showFormatDialog() {
  var formatListBox = document.getElementById("format-list");
  var formatId;
  if (formatListBox.selectedItem) {
    formatId = formatListBox.selectedItem.value;
  } else {
    formatId = 'default';
  }
  var formatInfo = this.formats.findFormat(formatId);

  document.getElementById("format-name").value = formatInfo.name;
  document.getElementById("delete-button").disabled = formatInfo.saveFormat ? false : true;
  document.getElementById("rename-button").disabled = formatInfo.saveFormat ? false : true;

  var configBox = document.getElementById("format-config");
  var format;
  try {
    format = formatInfo.loadFormatter();
  } catch (error) {
    setTimeout(function() {
      alert("an error occured: " + error + ", file=" + formatInfo.getFormatURI());
    }, 50);
    format = {};
  }
  var newConfigBox;
  if (format.createConfigForm) {
    newConfigBox = format.createConfigForm(document);
  } else {
    newConfigBox = document.createElement("box");
    newConfigBox.setAttribute("id", "format-config");
    var note = document.createElement("description");
    newConfigBox.appendChild(note);
    note.appendChild(document.createTextNode("There are no options."));
  }
  configBox.parentNode.replaceChild(newConfigBox, configBox);
  configBox = newConfigBox;
  if (format.options) {
    for (key in format.options) {
      var e = document.getElementById("options_" + key);
      if (e) {
        var value = this.options["formats." + formatInfo.id + "." + key];
        if (value == null) {
          value = format.options[key];
        }
        if (e.checked != undefined) {
          e.checked = 'true' == value;
        } else {
          e.value = value;
        }
      }
    }
  }
  var self = this;
  this.format = format;
  this.formatInfo = formatInfo;
  configBox.addEventListener("blur", function(event) {
    self.updateFormatOptions();
  }, true);
}

function openFormatSource() {
  this.formatInfo.saved = false;
  window.openDialog('chrome://selenium-ide/content/format-source-dialog.xul', 'options-format-source', 'chrome', this.formatInfo);
  if (this.formatInfo.saved) {
    updateFormatSelection();
  }
}

function createNewFormat() {
  var formatInfo = new UserFormat();
  window.openDialog('chrome://selenium-ide/content/format-source-dialog.xul', 'options-format-source', 'chrome', formatInfo);
  if (formatInfo.saved) {
    this.formats.reloadFormats();
    loadFormatList();
    selectFormat(formatInfo.id);
  }
}

function findFormatIndex(formatInfo) {
  return -1;
}

function deleteFormat() {
  if (!confirm("Really delete this format?")) {
    return;
  }
  var index = -1;
  if (!this.formatInfo) return;
  var formats = this.formats.userFormats;
  for (var i = 0; i < formats.length; i++) {
    if (formats[i].id == this.formatInfo.id) {
      index = i;
      break;
    }
  }
  if (index >= 0) {
    this.formats.removeUserFormatAt(index);
    this.formats.saveFormats();
    loadFormatList();
    selectFormat("default");
  }
}

function renameFormat() {
  var name = prompt("Enter new name", this.formatInfo.name);
  if (name) {
    this.formatInfo.name = name;
    this.formats.saveFormats();
    loadFormatList();
    selectFormat(this.formatInfo.id);
  }
}

function validate() {
  var hasError = false;
  for (var name in validations) {
    var e = document.getElementById(name);
    var result = validations[name](e.value);
    document.getElementById(name + 'Error').value = result;
    if (result) {
      hasError = true;
    }
  }
  document.documentElement.getButton("accept").disabled = hasError;
}

var validations = {
  encoding: function(value) {
    var result = "";
    try {
      encodingTestConverter.charset = value;
    } catch (error) {
      result = Message("error.invalidEncoding");
    }
    var encoding = value.toUpperCase();
    if (encoding == 'UTF-16' || encoding == 'UTF-32') {
      // Character encodings that contain null bytes are not supported.
      // See http://developer.mozilla.org/en/docs/Reading_textual_data
      // Let me know if there are other encodings.
      result = Message("error.encodingNotSupported", value);
    }
    return result;
  },

  timeout: function(value) {
    if (value.match(/^\d+$/)) {
      return '';
    } else {
      return Message("error.timeoutNotNumber");
    }
  }
};

/**
 * Return a list by id and clear it as well
 * @param listId
 */
function getClearedList(listId) {
  var list = document.getElementById(listId);
  for (var i = list.getRowCount() - 1; i >= 0; i--) {
    list.removeItemAt(i);
  }
  return list;
}

/**
 * Call the reload method of the current editor
 */
function reloadUserExtFile() {
  try {
    SeleniumIDE.Loader.getEditors()[0].reload();
  } catch(e) {
    alert("error :" + e);
  }
}

/**
 * Use to toggle the reload-button state
 */
function updateReloadButton(show) {
  document.getElementById('reload').hidden = !show;
  document.getElementById('reload').disabled = !show;
}

//
// plugins
//
function loadPluginList() {
  var list = getClearedList("plugin-list");
  this.plugins.forEach(function(p) {
    list.appendItem(p.name, p.id);
  });

  if (list.itemCount > 0) {
    selectPlugin(list.getItemAtIndex(0).value);
  }
}

function selectPlugin(id) {
  document.getElementById("plugin-list").value = id;
  showPluginInfo();
}

function updatePluginSelection() {
  showPluginInfo();
}

// TODO - this is a copy/paste from formats/html.js that needs to be moved to a common place
function copyElement(doc, element) {
  var copy = doc.createElement(element.nodeName.toLowerCase());
  var atts = element.attributes;
  var i;
  for (i = 0; atts != null && i < atts.length; i++) {
    copy.setAttribute(atts[i].name, atts[i].value);
  }
  var childNodes = element.childNodes;
  for (i = 0; i < childNodes.length; i++) {
    if (childNodes[i].nodeType == 1) { // element
      copy.appendChild(copyElement(doc, childNodes[i]));
    } else if (childNodes[i].nodeType == 3) { // text
      copy.appendChild(doc.createTextNode(childNodes[i].nodeValue));
    }
  }
  return copy;
}

function showPluginInfo() {
  var pluginId = document.getElementById("plugin-list").value;
  var pluginInfo = this.plugins.findPlugin(pluginId);

  var pluginName = document.getElementById("plugin-name");
  pluginName.value = pluginInfo.name;
  pluginName.className = '';
  var content = '<description>' + pluginInfo.description + '</description>';
  content += '<separator class="thin"/><description>Version ' + pluginInfo.version + '</description>';
  content += '<separator class="thin"/>' + '<description>Created By ' + pluginInfo.creator + '</description>';
  pluginInfo.developers.forEach(function (developer) {
    content += '<description>Developed by ' + developer.name + '</description>';
  });
  if (pluginInfo.homepageURL && pluginInfo.homepageURL.length > 0) {
    content += '<label class="text-link" href="' + pluginInfo.homepageURL + '" value="Visit website" />';
  }
  content += '<separator class="thin"/><checkbox id="disablePlugin" label="Disable code provided by this plugin"/>';
  content += '<description>A restart of Selenium IDE is required if changed.</description>';
  if (pluginInfo.data.options.autoDisabled) {
    pluginName.className = 'pluginError';
    content += '<description class="error">There were errors loading this plugin. The plugin provided code has been disabled. Clear the disabled checkbox if you think the problem has been solved. Please contact the plugin author for fixing this issue.</description>';
  }
  var errors = this.pluginErrors.getPluginErrors(pluginInfo.id);
  if (errors.length > 0) {
    content += '<textbox id="pluginErrors" multiline="true" readonly="true" rows="5" />';
  }
  var infoBox = document.getElementById("plugin-info");
  var xml = '<vbox id="plugin-info" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">' + content + '</vbox>';
  var parser = new DOMParser();
  var element = parser.parseFromString(xml, "text/xml").documentElement;
  // If we directly return this element, "permission denied" exception occurs
  // when the user clicks on the buttons or textboxes. I haven't figured out the reason,
  // but as a workaround I'll just re-create the element and make a deep copy.
  infoBox.parentNode.replaceChild(copyElement(document, element), infoBox);

  if (errors.length > 0) {
    //TODO improve error reporting by showing more errors and details
    document.getElementById('pluginErrors').value = errors[0].error;
  }
  var checkbox = document.getElementById('disablePlugin');
  checkbox.checked = pluginInfo.data.options.disabled;
  checkbox.addEventListener("click", function(event) {
    pluginInfo.data.options.disabled = checkbox.checked;
  }, false);
}
<commit_msg>Fixed options validation on options reset in IDE. Fixes issue 1050<commit_after>/*
 * Copyright 2005 Shinya Kasatani
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

this.Preferences = SeleniumIDE.Preferences;

function saveOptions() {
  var options = this.options;
  var name;
  for (name in options) {
    var e = document.getElementById(name);
    if (e != null) {
      options[name] = e.checked != undefined ? e.checked.toString() : e.value;
    }
  }
  if (options.enableExperimentalFeatures.toLowerCase() != 'true' && options.selectedFormat != 'default') {
    if (!confirm(Message("options.confirmFormatDisable"))) {
      return false;
    }
  }
  updateFormatOptions();
  options['locatorBuildersOrder'] = this.locatorBuilderList.getListItems().join(',');
  this.pluginManager.updatePlugins(this.plugins);
  this.pluginManager.save(options);
  SeleniumIDE.Loader.getEditors().forEach(function(editor) {
    editor.app.setOptions(options);
  });
  Preferences.save(options);
  return true;
}

function loadFromOptions(options) {
  var key;
  for (key in options) {
    var e = document.getElementById(key);
    if (e != null) {
      if (e.checked != undefined) {
        e.checked = options[key] == 'true';

        //initialize the reload-button state
        if (key == "showDeveloperTools") {
          updateReloadButton(e.checked);
        }
      } else {
        e.value = options[key];
      }
    }
  }
}

function loadDefaultOptions() {
  if (confirm(Message("options.confirmLoadDefaultOptions"))) {
    loadFromOptions(Preferences.DEFAULT_OPTIONS);
    validate()
    for (key in this.options) {
      if (/^formats\./.test(key)) {
        delete this.options[key];
      }
    }
    if (this.format) {
      showFormatDialog();
    }
    if (this.locatorBuilderList) {
      //By design the new locators will be temporarily lost. The missing locators will be automatically added when the options are saved
      this.locatorBuilderList.reload(options['locatorBuildersOrder'].split(','));
    }
    //TODO set plugin defaults
  }
}

function getPluginManager(options) {
  var editor = SeleniumIDE.Loader.getTopEditor();
  if (editor && editor.pluginManager) {
    return editor.pluginManager;
  }
  return new PluginManager(options);
}

function loadOptions() {
  var options = Preferences.load();
  this.options = options;
  loadFromOptions(options);
  this.pluginManager = getPluginManager(options);
  this.formats = new FormatCollection(options, this.pluginManager);

  loadFormatList();
  //Samit: Enh: Selected the options of the current format if available
  if (this.formats.findFormat(options.selectedFormat)) {
    selectFormat(options.selectedFormat);
  } else {
    selectFormat('default');
  }

  //Convert our normal listbox into a drag and drop reordered listbox
  this.locatorBuilderList = new DnDReorderedListbox(window.document.getElementById('locatorBuilder-list'), options['locatorBuildersOrder'].split(','));
  this.pluginErrors = this.pluginManager.errors;
  this.plugins = this.pluginManager.getPluginCollection();
//  this.plugins.callWhenReady(loadPluginList, this); //Samit: Enh: Work with async Addon Manager for Firefox 4
  loadPluginsWhenReady(this.plugins, this); //For some reason the above call stopped working, my guess is it is due to a different window context
}

function loadPluginsWhenReady(plugins, win) {
  if (!plugins.isReady()) {
    setTimeout(function() {
      loadPluginsWhenReady(plugins, win);
    }, 100);
  } else {
    win.loadPluginList();
  }
}

function loadFormatList() {
  var list = getClearedList("format-list");
  for (var j = 0; j < this.formats.formats.length; j++) {
    var format = this.formats.formats[j];
    list.appendItem(format.name, format.id);
  }
}

var encodingTestConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);

function chooseFile(target) {
  var nsIFilePicker = Components.interfaces.nsIFilePicker;
  var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
  fp.init(window, "Select extensions file", nsIFilePicker.modeOpen);
  fp.appendFilters(nsIFilePicker.filterAll);
  var res = fp.show();
  if (res == nsIFilePicker.returnOK) {
    var e = document.getElementById(target);
    if (e.value) {
      e.value += ', ' + fp.file.path;
    } else {
      e.value = fp.file.path;
    }
  }
}

function updateFormatOptions() {
  if (this.format && this.format.options) {
    for (key in this.format.options) {
      var e = document.getElementById("options_" + key);
      if (e) {
        if (e.checked != undefined) {
          this.options["formats." + this.formatInfo.id + "." + key] = e.checked.toString();
        } else {
          this.options["formats." + this.formatInfo.id + "." + key] = e.value;
        }
      }
    }
  }
}

function selectFormat(id) {
  document.getElementById("format-list").value = id;
  updateFormatSelection();
}

function updateFormatSelection() {
  this.updateFormatOptions();
  this.showFormatDialog();
}

function showFormatDialog() {
  var formatListBox = document.getElementById("format-list");
  var formatId;
  if (formatListBox.selectedItem) {
    formatId = formatListBox.selectedItem.value;
  } else {
    formatId = 'default';
  }
  var formatInfo = this.formats.findFormat(formatId);

  document.getElementById("format-name").value = formatInfo.name;
  document.getElementById("delete-button").disabled = formatInfo.saveFormat ? false : true;
  document.getElementById("rename-button").disabled = formatInfo.saveFormat ? false : true;

  var configBox = document.getElementById("format-config");
  var format;
  try {
    format = formatInfo.loadFormatter();
  } catch (error) {
    setTimeout(function() {
      alert("an error occured: " + error + ", file=" + formatInfo.getFormatURI());
    }, 50);
    format = {};
  }
  var newConfigBox;
  if (format.createConfigForm) {
    newConfigBox = format.createConfigForm(document);
  } else {
    newConfigBox = document.createElement("box");
    newConfigBox.setAttribute("id", "format-config");
    var note = document.createElement("description");
    newConfigBox.appendChild(note);
    note.appendChild(document.createTextNode("There are no options."));
  }
  configBox.parentNode.replaceChild(newConfigBox, configBox);
  configBox = newConfigBox;
  if (format.options) {
    for (key in format.options) {
      var e = document.getElementById("options_" + key);
      if (e) {
        var value = this.options["formats." + formatInfo.id + "." + key];
        if (value == null) {
          value = format.options[key];
        }
        if (e.checked != undefined) {
          e.checked = 'true' == value;
        } else {
          e.value = value;
        }
      }
    }
  }
  var self = this;
  this.format = format;
  this.formatInfo = formatInfo;
  configBox.addEventListener("blur", function(event) {
    self.updateFormatOptions();
  }, true);
}

function openFormatSource() {
  this.formatInfo.saved = false;
  window.openDialog('chrome://selenium-ide/content/format-source-dialog.xul', 'options-format-source', 'chrome', this.formatInfo);
  if (this.formatInfo.saved) {
    updateFormatSelection();
  }
}

function createNewFormat() {
  var formatInfo = new UserFormat();
  window.openDialog('chrome://selenium-ide/content/format-source-dialog.xul', 'options-format-source', 'chrome', formatInfo);
  if (formatInfo.saved) {
    this.formats.reloadFormats();
    loadFormatList();
    selectFormat(formatInfo.id);
  }
}

function findFormatIndex(formatInfo) {
  return -1;
}

function deleteFormat() {
  if (!confirm("Really delete this format?")) {
    return;
  }
  var index = -1;
  if (!this.formatInfo) return;
  var formats = this.formats.userFormats;
  for (var i = 0; i < formats.length; i++) {
    if (formats[i].id == this.formatInfo.id) {
      index = i;
      break;
    }
  }
  if (index >= 0) {
    this.formats.removeUserFormatAt(index);
    this.formats.saveFormats();
    loadFormatList();
    selectFormat("default");
  }
}

function renameFormat() {
  var name = prompt("Enter new name", this.formatInfo.name);
  if (name) {
    this.formatInfo.name = name;
    this.formats.saveFormats();
    loadFormatList();
    selectFormat(this.formatInfo.id);
  }
}

function validate() {
  var hasError = false;
  for (var name in validations) {
    var e = document.getElementById(name);
    var result = validations[name](e.value);
    document.getElementById(name + 'Error').value = result;
    if (result) {
      hasError = true;
    }
  }
  document.documentElement.getButton("accept").disabled = hasError;
}

var validations = {
  encoding: function(value) {
    var result = "";
    try {
      encodingTestConverter.charset = value;
    } catch (error) {
      result = Message("error.invalidEncoding");
    }
    var encoding = value.toUpperCase();
    if (encoding == 'UTF-16' || encoding == 'UTF-32') {
      // Character encodings that contain null bytes are not supported.
      // See http://developer.mozilla.org/en/docs/Reading_textual_data
      // Let me know if there are other encodings.
      result = Message("error.encodingNotSupported", value);
    }
    return result;
  },

  timeout: function(value) {
    if (value.match(/^\d+$/)) {
      return '';
    } else {
      return Message("error.timeoutNotNumber");
    }
  }
};

/**
 * Return a list by id and clear it as well
 * @param listId
 */
function getClearedList(listId) {
  var list = document.getElementById(listId);
  for (var i = list.getRowCount() - 1; i >= 0; i--) {
    list.removeItemAt(i);
  }
  return list;
}

/**
 * Call the reload method of the current editor
 */
function reloadUserExtFile() {
  try {
    SeleniumIDE.Loader.getEditors()[0].reload();
  } catch(e) {
    alert("error :" + e);
  }
}

/**
 * Use to toggle the reload-button state
 */
function updateReloadButton(show) {
  document.getElementById('reload').hidden = !show;
  document.getElementById('reload').disabled = !show;
}

//
// plugins
//
function loadPluginList() {
  var list = getClearedList("plugin-list");
  this.plugins.forEach(function(p) {
    list.appendItem(p.name, p.id);
  });

  if (list.itemCount > 0) {
    selectPlugin(list.getItemAtIndex(0).value);
  }
}

function selectPlugin(id) {
  document.getElementById("plugin-list").value = id;
  showPluginInfo();
}

function updatePluginSelection() {
  showPluginInfo();
}

// TODO - this is a copy/paste from formats/html.js that needs to be moved to a common place
function copyElement(doc, element) {
  var copy = doc.createElement(element.nodeName.toLowerCase());
  var atts = element.attributes;
  var i;
  for (i = 0; atts != null && i < atts.length; i++) {
    copy.setAttribute(atts[i].name, atts[i].value);
  }
  var childNodes = element.childNodes;
  for (i = 0; i < childNodes.length; i++) {
    if (childNodes[i].nodeType == 1) { // element
      copy.appendChild(copyElement(doc, childNodes[i]));
    } else if (childNodes[i].nodeType == 3) { // text
      copy.appendChild(doc.createTextNode(childNodes[i].nodeValue));
    }
  }
  return copy;
}

function showPluginInfo() {
  var pluginId = document.getElementById("plugin-list").value;
  var pluginInfo = this.plugins.findPlugin(pluginId);

  var pluginName = document.getElementById("plugin-name");
  pluginName.value = pluginInfo.name;
  pluginName.className = '';
  var content = '<description>' + pluginInfo.description + '</description>';
  content += '<separator class="thin"/><description>Version ' + pluginInfo.version + '</description>';
  content += '<separator class="thin"/>' + '<description>Created By ' + pluginInfo.creator + '</description>';
  pluginInfo.developers.forEach(function (developer) {
    content += '<description>Developed by ' + developer.name + '</description>';
  });
  if (pluginInfo.homepageURL && pluginInfo.homepageURL.length > 0) {
    content += '<label class="text-link" href="' + pluginInfo.homepageURL + '" value="Visit website" />';
  }
  content += '<separator class="thin"/><checkbox id="disablePlugin" label="Disable code provided by this plugin"/>';
  content += '<description>A restart of Selenium IDE is required if changed.</description>';
  if (pluginInfo.data.options.autoDisabled) {
    pluginName.className = 'pluginError';
    content += '<description class="error">There were errors loading this plugin. The plugin provided code has been disabled. Clear the disabled checkbox if you think the problem has been solved. Please contact the plugin author for fixing this issue.</description>';
  }
  var errors = this.pluginErrors.getPluginErrors(pluginInfo.id);
  if (errors.length > 0) {
    content += '<textbox id="pluginErrors" multiline="true" readonly="true" rows="5" />';
  }
  var infoBox = document.getElementById("plugin-info");
  var xml = '<vbox id="plugin-info" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">' + content + '</vbox>';
  var parser = new DOMParser();
  var element = parser.parseFromString(xml, "text/xml").documentElement;
  // If we directly return this element, "permission denied" exception occurs
  // when the user clicks on the buttons or textboxes. I haven't figured out the reason,
  // but as a workaround I'll just re-create the element and make a deep copy.
  infoBox.parentNode.replaceChild(copyElement(document, element), infoBox);

  if (errors.length > 0) {
    //TODO improve error reporting by showing more errors and details
    document.getElementById('pluginErrors').value = errors[0].error;
  }
  var checkbox = document.getElementById('disablePlugin');
  checkbox.checked = pluginInfo.data.options.disabled;
  checkbox.addEventListener("click", function(event) {
    pluginInfo.data.options.disabled = checkbox.checked;
  }, false);
}
<|endoftext|>