<commit_before>const Lang = imports.lang;
const Main = imports.ui.main;
const Mainloop = imports.mainloop;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Tweener = imports.ui.tweener;

const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Util = Me.imports.util;

function LOG(message) {
	// log("[pixel-saver]: " + message);
}

function WARN(message) {
	log("[pixel-saver]: " + message);
}

let appMenu = null;

/**
 * AppMenu synchronization
 */
function updateAppMenu() {
	let win = global.display.focus_window;
	if (!win) {
		return false;
	}

	let title = win.title;

	// Not the topmost maximized window.
	if (win !== Util.getWindow()) {
		let app = Shell.WindowTracker.get_default().get_window_app(win);
		title = app.get_name();
	}

	if (title.length > 32) {
     title = title.substr(0, 30) + '...';
  }
	title = title.replace(/\n/g, " ");

	LOG('Override title ' + title);
	appMenu._label.set_text(title);
	tooltip.text = title;

	return false;
}

/**
 * Track the focused window's title
 */
let activeWindow = null;
let awCallbackID = 0;
function changeActiveWindow(win) {
	if (win === activeWindow) {
		return;
	}

	if (activeWindow) {
		activeWindow.disconnect(awCallbackID);
	}

	activeWindow = win;

	if (win) {
		awCallbackID = win.connect('notify::title', updateAppMenu);
		updateAppMenu();
	}
}

/**
 * Focus change
 */
function onFocusChange() {
	let input_mode_check = (global.stage_input_mode === undefined)
		? true
		: global.stage_input_mode == Shell.StageInputMode.FOCUSED;
	if (!Shell.WindowTracker.get_default().focus_app && input_mode_check) {
		// If the app has just lost focus to the panel, pretend
		// nothing happened; otherwise you can't keynav to the
		// app menu.
		return false;
	}

	changeActiveWindow(global.display.focus_window);
	return false;
}

/**
 * tooltip
 */
let tooltip = null;
let showTooltip = false;

let SHOW_DELAY = 350;
let SHOW_DURATION = 0.15;
let HIDE_DURATION = 0.1;

let tooltipDelayCallbackID = 0;
let menuCallbackID = 0;

function resetMenuCallback() {
	if (menuCallbackID) {
		appMenu.menu.disconnect(menuCallbackID);
		menuCallbackID = 0;
	}
}

function onAppMenuHover(actor) {
	let hover = actor.get_hover();
	if (showTooltip === hover) {
		return false;
	}

	// We are not in the right state, let's fix that.
	showTooltip = hover;

	if (showTooltip) {
		tooltipDelayCallbackID = Mainloop.timeout_add(SHOW_DELAY, function() {
			if (!showTooltip) {
				WARN('showTooltip is false and delay callback ran.');
			}

			// Something wants us to stop.
			if (tooltipDelayCallbackID === 0) {
				return false;
			}

			let label = appMenu._label;
			if (label == null || !label.get_clutter_text().get_layout().is_ellipsized()) {
				// Do not need to hide.
				tooltipDelayCallbackID = 0;
				return false;
			}

			Main.uiGroup.add_actor(tooltip);

			resetMenuCallback();
			menuCallbackID = appMenu.menu.connect('open-state-changed', function(menu, open) {
				if (open) {
					Main.uiGroup.remove_actor(tooltip);
				} else {
					Main.uiGroup.add_actor(tooltip);
				}
			});

			[px, py] = Main.panel.actor.get_transformed_position();
			[bx, by] = label.get_transformed_position();
			[w, h] = label.get_transformed_size();

			let y = py + Main.panel.actor.get_height() + 3;
			let x = bx - Math.round((tooltip.get_width() - w)/2);
			tooltip.opacity = 0;
			tooltip.set_position(x, y);

			LOG('show title tooltip');

			Tweener.removeTweens(tooltip);
			Tweener.addTween(tooltip, {
				opacity: 255,
				time: SHOW_DURATION,
				transition: 'easeOutQuad',
			});

			return false;
		});
	} else if (tooltipDelayCallbackID > 0) {
		// If the event ran, then we hide.
		LOG('hide title tooltip');

		resetMenuCallback();

		Tweener.removeTweens(tooltip);
		Tweener.addTween(tooltip, {
			opacity: 0,
			time: HIDE_DURATION,
			transition: 'easeOutQuad',
			onComplete: function() {
				Main.uiGroup.remove_actor(tooltip);
			}
		});

		tooltipDelayCallbackID = 0;
	}

	return false;
}

/**
 * Subextension hooks
 */
function init() {}

let wmCallbackIDs = [];
let focusCallbackID = 0;
let tooltipCallbackID = 0;

function enable() {
	appMenu = Main.panel.statusArea.appMenu;

	tooltip = new St.Label({
		style_class: 'tooltip dash-label',
		text: '',
		opacity: 0
	});

	wmCallbackIDs = wmCallbackIDs.concat(Util.onSizeChange(updateAppMenu));

	focusCallbackID = global.display.connect('notify::focus-window', onFocusChange);
	tooltipCallbackID = appMenu.actor.connect('notify::hover', onAppMenuHover);
}

function disable() {
	wmCallbackIDs.forEach(function(id) {
		global.window_manager.disconnect(id);
	});

	wmCallbackIDs = [];

	global.display.disconnect(focusCallbackID);
	focusCallbackID = 0;

	appMenu.actor.disconnect(tooltipCallbackID);
	tooltipCallbackID = 0;

	if (activeWindow) {
		activeWindow.disconnect(awCallbackID);
		awCallbackID = 0;
		activeWindow = null;
	}

	if (tooltipDelayCallbackID) {
		Mainloop.source_remove(tooltipDelayCallbackID);
		tooltipDelayCallbackID = 0;
	}

	resetMenuCallback();

	tooltip.destroy();
	tooltip = null;
}

<commit_msg>Limit to 64 characters<commit_after>const Lang = imports.lang;
const Main = imports.ui.main;
const Mainloop = imports.mainloop;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Tweener = imports.ui.tweener;

const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Util = Me.imports.util;

function LOG(message) {
	// log("[pixel-saver]: " + message);
}

function WARN(message) {
	log("[pixel-saver]: " + message);
}

let appMenu = null;

/**
 * AppMenu synchronization
 */
function updateAppMenu() {
	let win = global.display.focus_window;
	if (!win) {
		return false;
	}

	let title = win.title;

	// Not the topmost maximized window.
	if (win !== Util.getWindow()) {
		let app = Shell.WindowTracker.get_default().get_window_app(win);
		title = app.get_name();
	}

	if (title.length > 64) {
     title = title.substr(0, 62) + '...';
  }
	title = title.replace(/\n/g, " ");

	LOG('Override title ' + title);
	appMenu._label.set_text(title);
	tooltip.text = title;

	return false;
}

/**
 * Track the focused window's title
 */
let activeWindow = null;
let awCallbackID = 0;
function changeActiveWindow(win) {
	if (win === activeWindow) {
		return;
	}

	if (activeWindow) {
		activeWindow.disconnect(awCallbackID);
	}

	activeWindow = win;

	if (win) {
		awCallbackID = win.connect('notify::title', updateAppMenu);
		updateAppMenu();
	}
}

/**
 * Focus change
 */
function onFocusChange() {
	let input_mode_check = (global.stage_input_mode === undefined)
		? true
		: global.stage_input_mode == Shell.StageInputMode.FOCUSED;
	if (!Shell.WindowTracker.get_default().focus_app && input_mode_check) {
		// If the app has just lost focus to the panel, pretend
		// nothing happened; otherwise you can't keynav to the
		// app menu.
		return false;
	}

	changeActiveWindow(global.display.focus_window);
	return false;
}

/**
 * tooltip
 */
let tooltip = null;
let showTooltip = false;

let SHOW_DELAY = 350;
let SHOW_DURATION = 0.15;
let HIDE_DURATION = 0.1;

let tooltipDelayCallbackID = 0;
let menuCallbackID = 0;

function resetMenuCallback() {
	if (menuCallbackID) {
		appMenu.menu.disconnect(menuCallbackID);
		menuCallbackID = 0;
	}
}

function onAppMenuHover(actor) {
	let hover = actor.get_hover();
	if (showTooltip === hover) {
		return false;
	}

	// We are not in the right state, let's fix that.
	showTooltip = hover;

	if (showTooltip) {
		tooltipDelayCallbackID = Mainloop.timeout_add(SHOW_DELAY, function() {
			if (!showTooltip) {
				WARN('showTooltip is false and delay callback ran.');
			}

			// Something wants us to stop.
			if (tooltipDelayCallbackID === 0) {
				return false;
			}

			let label = appMenu._label;
			if (label == null || !label.get_clutter_text().get_layout().is_ellipsized()) {
				// Do not need to hide.
				tooltipDelayCallbackID = 0;
				return false;
			}

			Main.uiGroup.add_actor(tooltip);

			resetMenuCallback();
			menuCallbackID = appMenu.menu.connect('open-state-changed', function(menu, open) {
				if (open) {
					Main.uiGroup.remove_actor(tooltip);
				} else {
					Main.uiGroup.add_actor(tooltip);
				}
			});

			[px, py] = Main.panel.actor.get_transformed_position();
			[bx, by] = label.get_transformed_position();
			[w, h] = label.get_transformed_size();

			let y = py + Main.panel.actor.get_height() + 3;
			let x = bx - Math.round((tooltip.get_width() - w)/2);
			tooltip.opacity = 0;
			tooltip.set_position(x, y);

			LOG('show title tooltip');

			Tweener.removeTweens(tooltip);
			Tweener.addTween(tooltip, {
				opacity: 255,
				time: SHOW_DURATION,
				transition: 'easeOutQuad',
			});

			return false;
		});
	} else if (tooltipDelayCallbackID > 0) {
		// If the event ran, then we hide.
		LOG('hide title tooltip');

		resetMenuCallback();

		Tweener.removeTweens(tooltip);
		Tweener.addTween(tooltip, {
			opacity: 0,
			time: HIDE_DURATION,
			transition: 'easeOutQuad',
			onComplete: function() {
				Main.uiGroup.remove_actor(tooltip);
			}
		});

		tooltipDelayCallbackID = 0;
	}

	return false;
}

/**
 * Subextension hooks
 */
function init() {}

let wmCallbackIDs = [];
let focusCallbackID = 0;
let tooltipCallbackID = 0;

function enable() {
	appMenu = Main.panel.statusArea.appMenu;

	tooltip = new St.Label({
		style_class: 'tooltip dash-label',
		text: '',
		opacity: 0
	});

	wmCallbackIDs = wmCallbackIDs.concat(Util.onSizeChange(updateAppMenu));

	focusCallbackID = global.display.connect('notify::focus-window', onFocusChange);
	tooltipCallbackID = appMenu.actor.connect('notify::hover', onAppMenuHover);
}

function disable() {
	wmCallbackIDs.forEach(function(id) {
		global.window_manager.disconnect(id);
	});

	wmCallbackIDs = [];

	global.display.disconnect(focusCallbackID);
	focusCallbackID = 0;

	appMenu.actor.disconnect(tooltipCallbackID);
	tooltipCallbackID = 0;

	if (activeWindow) {
		activeWindow.disconnect(awCallbackID);
		awCallbackID = 0;
		activeWindow = null;
	}

	if (tooltipDelayCallbackID) {
		Mainloop.source_remove(tooltipDelayCallbackID);
		tooltipDelayCallbackID = 0;
	}

	resetMenuCallback();

	tooltip.destroy();
	tooltip = null;
}

<|endoftext|>


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

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,
    optional: true,
    autoform: {
      editable: true,
      type: "bootstrap-url"
    }
  },
  title: {
    type: String,
    optional: false,
    autoform: {
      editable: true
    }
  },
  body: {
    type: String,
    optional: 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);

// ------------------------------------------------------------------------------------------- //
// ----------------------------------------- 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);
};

// we need the current user so we know who to upvote the existing post as
checkForPostsWithSameUrl = function (url, currentUser) {

  // 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'){
    upvoteItem(Posts, postWithSameLink, currentUser);

    // 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);
  }
}

// when on a post page, return the current post
currentPost = function () {
  return Posts.findOne(Router.current().data()._id);
}

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

Posts.before.insert(function (userId, doc) {
  if(!!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));
  }
});

postAfterSubmitMethodCallbacks.push(function (post) {

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

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

  return post;

});

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

submitPost = function (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, user);

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

  post._id = Posts.insert(post);

  // --------------------- Server-Side Async Callbacks --------------------- //

  if (Meteor.isServer) {
    Meteor.defer(function () { // use defer to avoid holding up client
      // run all post submit server callbacks on post object successively
      post = postAfterSubmitMethodCallbacks.reduce(function(result, currentFunction) {
          return currentFunction(result);
      }, 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 || !can.post(user))
      throw new Meteor.Error(601, i18n.t('you_need_to_login_or_be_invited_to_post_new_stories'));

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

    if(!hasAdminRights){

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

      // check that user waits more than X seconds between posts
      if(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(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, go over each schema property and clear it if it's not editable
    if (!hasAdminRights) {
      _.keys(post).forEach(function (propertyName) {
        var property = postSchemaObject[propertyName];
        if (!property || !property.autoform || !property.autoform.editable) {
          console.log("// Disallowed property detected: "+propertyName+" (nice try!)");
          delete post[propertyName]
        }
      });
    }

    // 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 (post, modifier, postId) {

    var user = Meteor.user();

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

    // check that user can edit
    if (!user || !can.edit(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 modifier successively
    modifier = postEditMethodCallbacks.reduce(function(result, currentFunction) {
        return currentFunction(result);
    }, modifier);

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

    Posts.update(postId, modifier);

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

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

    // ------------------------------ 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});

      // --------------------- Server-Side Async Callbacks --------------------- //
      if (Meteor.isServer) {
        Meteor.defer(function () { // use defer to avoid holding up client
          // run all post submit server callbacks on post object successively
          post = postApproveCallbacks.reduce(function(result, currentFunction) {
              return currentFunction(result);
          }, post);
        });
      }

    }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() || !can.editById(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>whoops<commit_after>
// ------------------------------------------------------------------------------------------- //
// ----------------------------------------- Schema ----------------------------------------- //
// ------------------------------------------------------------------------------------------- //

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,
    optional: true,
    autoform: {
      editable: true,
      type: "bootstrap-url"
    }
  },
  title: {
    type: String,
    optional: false,
    autoform: {
      editable: true
    }
  },
  body: {
    type: String,
    optional: 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);

// ------------------------------------------------------------------------------------------- //
// ----------------------------------------- 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);
};

// we need the current user so we know who to upvote the existing post as
checkForPostsWithSameUrl = function (url, currentUser) {

  // 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'){
    upvoteItem(Posts, postWithSameLink, currentUser);

    // 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);
  }
}

// when on a post page, return the current post
currentPost = function () {
  return Posts.findOne(Router.current().data()._id);
}

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

Posts.before.insert(function (userId, doc) {
  if(!!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));
  }
});

postAfterSubmitMethodCallbacks.push(function (post) {

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

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

  return post;

});

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

submitPost = function (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, user);

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

  post._id = Posts.insert(post);

  // --------------------- Server-Side Async Callbacks --------------------- //

  if (Meteor.isServer) {
    Meteor.defer(function () { // use defer to avoid holding up client
      // run all post submit server callbacks on post object successively
      post = postAfterSubmitMethodCallbacks.reduce(function(result, currentFunction) {
          return currentFunction(result);
      }, 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 || !can.post(user))
      throw new Meteor.Error(601, i18n.t('you_need_to_login_or_be_invited_to_post_new_stories'));

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

    if(!hasAdminRights){

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

      // check that user waits more than X seconds between posts
      if(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(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, go over each schema property and clear it if it's not editable
    if (!hasAdminRights) {
      _.keys(post).forEach(function (propertyName) {
        var property = postSchemaObject[propertyName];
        if (!property || !property.autoform || !property.autoform.editable) {
          console.log("// Disallowed property detected: "+propertyName+" (nice try!)");
          delete post[propertyName]
        }
      });
    }

    // 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 (post, modifier, postId) {

    var user = Meteor.user();

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

    // check that user can edit
    if (!user || !can.edit(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 modifier successively
    modifier = postEditMethodCallbacks.reduce(function(result, currentFunction) {
        return currentFunction(result);
    }, modifier);

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

    Posts.update(postId, modifier);

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

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

    // ------------------------------ 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});

      // --------------------- Server-Side Async Callbacks --------------------- //
      if (Meteor.isServer) {
        Meteor.defer(function () { // use defer to avoid holding up client
          // run all post submit server callbacks on post object successively
          post = postApproveCallbacks.reduce(function(result, currentFunction) {
              return currentFunction(result);
          }, post);
        });
      }

    }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() || !can.editById(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>// schema --------------------------------------------

var Schema = {};

Schema.UserProfileNotifications = new SimpleSchema({
  enabled: { // enable notifications
    type: Boolean
  },
  comments: { // new comments in own topic
    type: Boolean
  },
  replies: { // new replies to own comments
    type: Boolean
  },
  followers: { // new followers
    type: Boolean
  },
  'followingTopics.comments': { // new comments in following topics
    type: Boolean
  },
  'followingUsers.comments': { // new comments by following users
    type: Boolean
  },
  'followingUsers.topics': { // new topics by following users
    type: Boolean
  }
});

Schema.UserProfile = new SimpleSchema({
  name: {
    type: String,
    regEx: /^([a-zA-Z]+(\s{0,1})[a-zA-Z0-9.'-]+(\s{0,1})[a-zA-Z0-9.'-]*){3,25}$/
  },
  bio: {
    type: String,
    max: 100,
    optional: true
  },
  notifications: {
    type: Schema.UserProfileNotifications,
    optional: true
  }
});

Schema.User = new SimpleSchema({
  _id: {
    type: String,
    optional: true
  },
  emails: {
    type: [Object],
    optional: true
  },
  'emails.$.address': {
    type: String,
    regEx: SimpleSchema.RegEx.Email
  },
  'emails.$.verified': {
    type: Boolean
  },
  createdAt: {
    type: Date
  },
  stats: { // public but not modifiable
    type: Object,
    optional: true,
    blackbox: true
  },
  profile: { // public and modifiable
    type: Schema.UserProfile
  },
  activity: { // public but not modifiable
    type: Object,
    optional: true,
    blackbox: true
  },
  notifications: { // public but not modifiable
    type: Object,
    optional: true,
    blackbox: true
  },
  services: {
    type: Object,
    blackbox: true
  }
});

Meteor.users.attachSchema(Schema.User);

// end schema ----------------------------------------

// search --------------------------------------------

Meteor.users.initEasySearch('profile.name', {
  'limit': 20,
  'use': 'mongo-db'
});

// end search ----------------------------------------

// permissions ---------------------------------------

Meteor.users.deny({
  update: function (userId, user, fields) {
    // if(isAdminById(userId)) return false;

    // deny the update if it contains something other than the profile field
    return (_.without(fields, 'profile').length > 0);
  }
});

Meteor.users.allow({
  update: function (userId, user) {
    // return isAdminById(userId) || userId == user._id;
    return userId == user._id;
  },
  remove: function (userId, user) {
    // return isAdminById(userId) || userId == user._id;
    return userId = user._id;
  }
});

// end permissions -----------------------------------

// methods -------------------------------------------

// Meteor.methods({
//   newFollower: function (following) {
//     var userId = this.userId;

//     if (!userId || !canFollowById(userId))
//       throw new Meteor.Error(403, 'Please login to follow other users.');

//     // update user being followed
//     Meteor.users.update(userId, {
//       $addToSet: { 'activity.followingUsers': following }
//     });

//     // update the user who is following
//     Meteor.users.update(following, {
//       $addToSet: { 'activity.followers': userId },
//       $inc: { 'stats.followersCount': 1 }
//     });
//   },
//   removeFollower: function (following) {
//     var userId = this.userId;

//     if (!userId || !canFollowById(userId))
//       throw new Meteor.Error(403, 'Please login to follow other users.');

//     // update user being followed
//     Meteor.users.update(userId, {
//       $pull: { 'activity.followingUsers': following }
//     });

//     // update the user who is following
//     Meteor.users.update(following, {
//       $pull: { 'activity.followers': userId },
//       $inc: { 'stats.followersCount': -1 }
//     });
//   }
// });

// end methods ---------------------------------------

<commit_msg>Add schema for user stats<commit_after>// schema --------------------------------------------

var Schema = {};

Schema.UserProfileNotifications = new SimpleSchema({
  enabled: { // enable notifications
    type: Boolean
  },
  comments: { // new comments in own topic
    type: Boolean
  },
  replies: { // new replies to own comments
    type: Boolean
  },
  followers: { // new followers
    type: Boolean
  },
  'followingTopics.comments': { // new comments in following topics
    type: Boolean
  },
  'followingUsers.comments': { // new comments by following users
    type: Boolean
  },
  'followingUsers.topics': { // new topics by following users
    type: Boolean
  }
});

Schema.UserProfile = new SimpleSchema({
  name: {
    type: String,
    regEx: /^([a-zA-Z]+(\s{0,1})[a-zA-Z0-9.'-]+(\s{0,1})[a-zA-Z0-9.'-]*){3,25}$/
  },
  bio: {
    type: String,
    max: 100,
    optional: true
  },
  notifications: {
    type: Schema.UserProfileNotifications,
    optional: true
  }
});

Schema.UserStats = new SimpleSchema({
  commentsCount: {
    type: Number,
    min: 0
  },
  followersCount: {
    type: Number,
    min: 0
  },
  reputation: {
    type: Number,
    min: 0 // possibly negative if we have downvotes ?
  },
  topicsCount: {
    type: Number,
    min: 0
  }
});

Schema.User = new SimpleSchema({
  _id: {
    type: String,
    optional: true
  },
  emails: {
    type: [Object],
    optional: true
  },
  'emails.$.address': {
    type: String,
    regEx: SimpleSchema.RegEx.Email
  },
  'emails.$.verified': {
    type: Boolean
  },
  createdAt: {
    type: Date
  },
  stats: { // public but not modifiable
    type: Schema.UserStats,
    optional: true
  },
  profile: { // public and modifiable
    type: Schema.UserProfile
  },
  activity: { // public but not modifiable
    type: Object,
    optional: true,
    blackbox: true
  },
  notifications: { // public but not modifiable
    type: Object,
    optional: true,
    blackbox: true
  },
  services: {
    type: Object,
    blackbox: true
  }
  // ,
  // roles: {
  //   type: Object,
  //   optional: true,
  //   blackbox: true
  // }
});

Meteor.users.attachSchema(Schema.User);

// end schema ----------------------------------------

// search --------------------------------------------

Meteor.users.initEasySearch('profile.name', {
  'limit': 20,
  'use': 'mongo-db'
});

// end search ----------------------------------------

// permissions ---------------------------------------

Meteor.users.deny({
  update: function (userId, user, fields) {
    // if(isAdminById(userId)) return false;

    // deny the update if it contains something other than the profile field
    return (_.without(fields, 'profile').length > 0);
  }
});

Meteor.users.allow({
  update: function (userId, user) {
    // return isAdminById(userId) || userId == user._id;
    return userId == user._id;
  },
  remove: function (userId, user) {
    // return isAdminById(userId) || userId == user._id;
    return userId = user._id;
  }
});

// end permissions -----------------------------------

// methods -------------------------------------------

// Meteor.methods({
//   newFollower: function (following) {
//     var userId = this.userId;

//     if (!userId || !canFollowById(userId))
//       throw new Meteor.Error(403, 'Please login to follow other users.');

//     // update user being followed
//     Meteor.users.update(userId, {
//       $addToSet: { 'activity.followingUsers': following }
//     });

//     // update the user who is following
//     Meteor.users.update(following, {
//       $addToSet: { 'activity.followers': userId },
//       $inc: { 'stats.followersCount': 1 }
//     });
//   },
//   removeFollower: function (following) {
//     var userId = this.userId;

//     if (!userId || !canFollowById(userId))
//       throw new Meteor.Error(403, 'Please login to follow other users.');

//     // update user being followed
//     Meteor.users.update(userId, {
//       $pull: { 'activity.followingUsers': following }
//     });

//     // update the user who is following
//     Meteor.users.update(following, {
//       $pull: { 'activity.followers': userId },
//       $inc: { 'stats.followersCount': -1 }
//     });
//   }
// });

// end methods ---------------------------------------

<|endoftext|>


<commit_before>var Schema = {};

Schema.User = new SimpleSchema({
  _id: {
    type: String,
    optional: false
  },
  firstName: {
    type: String
  },
  lastName: {
    type: String
  },
  isAdmin: {
    type: Boolean,
    defaultValue: false,
  },
  createdAt: { type: Date },
  twitterUrl: {
    type: String,
    optional: true
  },
  linkedInUrl: { type: String },
  skills: {
    type: [String]
  },
  listedAs: {
    type: [String],
    defaultValue: []
  },
  lookingFor: {
    type: [String],
    defaultValue: []
  },
  interests: {
    type: [String]
  },
  headline: { type: String, optional: true },
  summary: { type: String, optional: true },
  jobExperience: {
    type: [String],
    defaultValue: []
  },
  email: {
    type: String ,
    regEx: SimpleSchema.RegEx.Email,
    unique: true,
  },
  pictureUrl: { type: String, optional: true },
  largePictureUrl: { type: String, optional: true },
  location: {
    type: [Number],
    decimal: true,
    index: '2d',
    optional: true
  },
  isHidden: { type: Boolean, defaultValue: false },
  numVotesReceived: {
    type: Number,
    defaultValue: 0
  },
  numVotesCast: {
    type: Number,
    defaultValue: 0
  },
  lastActiveAt: {
    type: Date,
    optional: true
  },
  isTutorialComplete: {
    type: Boolean,
    defaultValue: false
  },
  services: {
    type: Object,
    optional: true,
    blackbox: true
  }
});

Meteor.users.attachSchema(Schema.User);

Meteor.users.allow({
  update: function(userId, userProp, fieldNames, modifier) {
    // Remove from the fields names the protected fields.
    // If there is nothing left, then deny the update.
    if (userId === Meteor.userId()) {
      // Allow _.without to accept an array as an argument.
      var validFieldsToUpdate = _.without.apply(_, [fieldNames].concat(Constants.PROTECTED_USER_FIELDS));
      if (validFieldsToUpdate.length > 0) {
        return true;
      }
    }
    return false;
  }
});
<commit_msg>Add isSubscribedToMsgNotif as a parameter<commit_after>var Schema = {};

Schema.User = new SimpleSchema({
  _id: {
    type: String,
    optional: false
  },
  firstName: {
    type: String
  },
  lastName: {
    type: String
  },
  isAdmin: {
    type: Boolean,
    defaultValue: false,
  },
  createdAt: { type: Date },
  twitterUrl: {
    type: String,
    optional: true
  },
  linkedInUrl: { type: String },
  skills: {
    type: [String]
  },
  listedAs: {
    type: [String],
    defaultValue: []
  },
  lookingFor: {
    type: [String],
    defaultValue: []
  },
  interests: {
    type: [String]
  },
  headline: { type: String, optional: true },
  summary: { type: String, optional: true },
  jobExperience: {
    type: [String],
    defaultValue: []
  },
  email: {
    type: String ,
    regEx: SimpleSchema.RegEx.Email,
    unique: true,
  },
  pictureUrl: { type: String, optional: true },
  largePictureUrl: { type: String, optional: true },
  location: {
    type: [Number],
    decimal: true,
    index: '2d',
    optional: true
  },
  isHidden: { type: Boolean, defaultValue: false },
  numVotesReceived: {
    type: Number,
    defaultValue: 0
  },
  numVotesCast: {
    type: Number,
    defaultValue: 0
  },
  lastActiveAt: {
    type: Date,
    optional: true
  },
  isTutorialComplete: {
    type: Boolean,
    defaultValue: false
  },
  isSubscribedToMsgNotif: {
    type: Boolean,
    defaultValue: true
  },
  services: {
    type: Object,
    optional: true,
    blackbox: true
  }
});

Meteor.users.attachSchema(Schema.User);

Meteor.users.allow({
  update: function(userId, userProp, fieldNames, modifier) {
    // Remove from the fields names the protected fields.
    // If there is nothing left, then deny the update.
    if (userId === Meteor.userId()) {
      // Allow _.without to accept an array as an argument.
      var validFieldsToUpdate = _.without.apply(_, [fieldNames].concat(Constants.PROTECTED_USER_FIELDS));
      if (validFieldsToUpdate.length > 0) {
        return true;
      }
    }
    return false;
  }
});
<|endoftext|>


<commit_before>const ms = require('ms');
const Discord = require('discord.js');

exports.run = (bot, message, args) => {
  let time = args.join(' ');
  let validUnlocks = ['release', 'unlock']
  const channel = message.guild.channels.find('name', 'mod-log');
  if (!channel) return message.reply("Make sure you have a channel called mod-log!");
  if (!time) return message.reply("You need the duriation of the time in either seconds, minutes, or hours");
  if (validUnlocks.includes(time)) {
    message.channel.overwritePermissions(message.guild.id, {
      SEND_MESSAGES: true
    }).then(() => {
      message.channel.send("Lockdown has ended, you are now freely to send messages");
      clearTimeout(bot.lockit[message.channel.id]);
    }).catch(error => {
console.log(error);
});
} else {
  message.channel.overwritePermissions(message.guild.id, {
    SEND_MESSAGES: false
    }).then(() => {
    message.channel.send(`Alrighty! Lockdown has started for this channel for ${ms(ms(time), { long:true })}`)
    var embed = new Discord.RichEmbed()
    .setTitle("Lockdown")
    .addField(`Lockdown by ${message.author.username}`, `Lasts for ${ms(ms(time), { long:true })}`)
channel.send({ embed: embed }).then(() => {

      bot.lockit[message.channel.id] = setTimeout(() => {
        message.channel.overwritePermissions(message.guild.id, {
        SEND_MESSAGES: true
      }).then(message.channel.send("Lockdown has ended, you are now freely to send what you want"))
      delete bot.lockit[message.channel.id];
    }, ms(time));
  }).catch(error => {
    console.log(error)
  });
});
    }};
<commit_msg>Update lockdown.js<commit_after>const ms = require('ms');
const Discord = require('discord.js');

exports.run = (bot, message, args, channel) => {
  let time = args.join(' ');
  let validUnlocks = ['release', 'unlock']
  const channel = message.guild.channels.find('name', 'mod-log');
  if (!channel) return message.reply("Make sure you have a channel called mod-log!");
  if (!time) return message.reply("You need the duriation of the time in either seconds, minutes, or hours");
  if (validUnlocks.includes(time)) {
    message.channel.overwritePermissions(message.guild.id, {
      SEND_MESSAGES: true
    }).then(() => {
      message.channel.send("Lockdown has ended, you are now freely to send messages");
      clearTimeout(bot.lockit[message.channel.id]);
    }).catch(error => {
console.log(error);
});
} else {
  message.channel.overwritePermissions(message.guild.id, {
    SEND_MESSAGES: false
    }).then(() => {
    message.channel.send(`Alrighty! Lockdown has started for this channel for ${ms(ms(time), { long:true })}`)
    var embed = new Discord.RichEmbed()
    .setTitle("Lockdown")
    .setColor("#80d4ff")
    .setThumbnail("http://www.streetgangs.com/wp-content/uploads/2010/03/behind_bars_xsmall.jpg")
    .addField(`Lockdown by ${message.author.username}`, `Lasts for ${ms(ms(time), { long:true })}`)
channel.send({ embed: embed }).then(() => {

      bot.lockit[message.channel.id] = setTimeout(() => {
        message.channel.overwritePermissions(message.guild.id, {
        SEND_MESSAGES: true
      }).then(message.channel.send("Lockdown has ended, you are now freely to send what you want"))
      delete bot.lockit[message.channel.id];
    }, ms(time));
  var embed2 = new Discord.RichEmbed()
  .setTitle("Lockdown Ended")
.setColor("#80d4ff")
  .setThumbnail("https://news.pindula.co.zw/wp-content/uploads/2017/01/Prison-break.jpg")
  .addField("Lockdown has ended", "Users are free to talk")
  channel.send({ embed: embed2 })
  }).catch(error => {
    console.log(error)
  });
});
    }};
<|endoftext|>


<commit_before>const Discord = require('discord.js');
const Util = require('../classes/Util.js');

module.exports.name = 'warnlist';
module.exports.alias = [
  'warnlist',
  'warnlog'
];

module.exports.isAllowed = (message, server) => {
  return server.hiddenChannels.includes(message.channel.id);
};

module.exports.help = 'Warnings for the user `,warnlist [User]`';

module.exports.command = async (message, content, bot, server) => {
  if (content == '') {
    const warnings = server.warnlist;
    const list = [];
    for (let u of Object.keys(warnings)) {
      const warns = warnings[u].length;
      const user = await bot.users.fetch(u);
      list.push(`<@${u}> ${user.tag}: ${warns} warning${warns === 1 ? '' : 's'}`)
    }
    Util.paginate(message.channel, 'All warnings', list, 10, message.author.id)
    return;
  }

  let userID;
  let mentions = message.mentions.users;
  if (mentions.size != 0) {
    userID = mentions.first().id;
  } else {
    const idMatch = content.match(Util.REGEX_RAW_ID);
    if (idMatch) {
      userID = idMatch[0];
    }
  }

  if (!userID) {
    message.channel.send('Failed to get a user');
    return;
  };

  if (server.warnlist[userID]) {
    const warnings = server.warnlist[userID];
    const embed = new Discord.MessageEmbed();
    let member = await server.guild.member(userID);
    embed.title = `Warning list for ${member ? member.user.tag : userID}`;
    embed.color = Number('0xDB3C3C');
    for (let { issued, issuer, warnMessage, link, silent } of warnings) {
      const issuerMember = await server.guild.member(issuer);
      if (link) {
        warnMessage += `\n[jump](${link})`;
      }
      if (silent) {
        warnMessage += `\n(Silently logged)`;
      }
      embed.addField(`${new Date(issued).toGMTString()} by ${issuerMember ? issuerMember.user.tag : issuer }`, warnMessage, false);
    }
    message.channel.send({ embed })
  } else {
    message.channel.send('No warnings found');
  }
};
<commit_msg>warn log overflow<commit_after>const Discord = require('discord.js');
const Util = require('../classes/Util.js');

module.exports.name = 'warnlist';
module.exports.alias = [
  'warnlist',
  'warnlog'
];

module.exports.isAllowed = (message, server) => {
  return server.hiddenChannels.includes(message.channel.id);
};

module.exports.help = 'Warnings for the user `,warnlist [User]`';

module.exports.command = async (message, content, bot, server) => {
  if (content == '') {
    const warnings = server.warnlist;
    const list = [];
    for (let u of Object.keys(warnings)) {
      const warns = warnings[u].length;
      const user = await bot.users.fetch(u);
      list.push(`<@${u}> ${user.tag}: ${warns} warning${warns === 1 ? '' : 's'}`)
    }
    Util.paginate(message.channel, 'All warnings', list, 10, message.author.id)
    return;
  }

  let userID;
  let mentions = message.mentions.users;
  if (mentions.size != 0) {
    userID = mentions.first().id;
  } else {
    const idMatch = content.match(Util.REGEX_RAW_ID);
    if (idMatch) {
      userID = idMatch[0];
    }
  }

  if (!userID) {
    message.channel.send('Failed to get a user');
    return;
  };

  if (server.warnlist[userID]) {
    const warnings = server.warnlist[userID];
    const embed = new Discord.MessageEmbed();
    let member = await server.guild.member(userID);
    embed.title = `Warning list for ${member ? member.user.tag : userID}`;
    embed.color = Number('0xDB3C3C');
    let count = 0;
    for (let { issued, issuer, warnMessage, link, silent } of warnings) {
      const issuerMember = await server.guild.member(issuer);
      if (link) {
        warnMessage += `\n[jump](${link})`;
      }
      if (silent) {
        warnMessage += `\n(Silently logged)`;
      }
      if (++count > 25) {
        await message.channel.send({ embed });
        embed.fields = [];
        count = 1;
      }
      embed.addField(`${new Date(issued).toGMTString()} by ${issuerMember ? issuerMember.user.tag : issuer }`, warnMessage, false);
    }
    message.channel.send({ embed })
  } else {
    message.channel.send('No warnings found');
  }
};
<|endoftext|>


<commit_before>/**
 * @file  Base JavaScript needed independent of the project
 * @author Michael Kühnel <m.kuehnel@micromata.de>
 */

/**
 * The jQuery object or a jQuery set containing on or more DOM elements.
 * @typedef {Object} jQuery
 */

/**
 * Namespace of the app.
 * This should be only variable the app should store in the global scope.
 * @namespace kickstarter
 * @todo Rename the global variable throughout the whole file according to your needs.
 * @example
 * yourAppName = window.yourAppName || {};
 * @todo Update documentation after renaming.
 */
var kickstarter = window.kickstarter || {};

/**
 * Namespace of the base module. Contains JavaScript needed independent of the
 * project or other modules.
 * @namespace base
 * @memberof kickstarter
 */
kickstarter.base = (function () {
	'use strict';

	/**
	 * Fix viewport issues with IE 10.
	 * @see {@link http://getbootstrap.com/getting-started/#support-ie10-width}
	 * @memberof kickstarter.base
	 * @private
	 */
	(function () {
		if (navigator.userAgent.match(/IEMobile\/10\.0/)) {
			var msViewportStyle = document.createElement('style');
			msViewportStyle.appendChild(
				document.createTextNode(
					'@-ms-viewport{width:auto!important}'
				)
			);
			document.querySelector('head').appendChild(msViewportStyle);
		}
	})();

	/**
	 * Avoid `console` errors in browsers that lack a console.
	 * @see {@link https://github.com/h5bp/html5-boilerplate/blob/master/js/plugins.js}
	 * @memberof kickstarter.base
	 * @private
	 */
	(function () {
		var method;
		var noop = function () {};
		var methods = [
			'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
			'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
			'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
			'timeStamp', 'trace', 'warn'
		];
		var length = methods.length;
		var console = (window.console = window.console || {});

		while (length--) {
			method = methods[length];

			// Only stub undefined methods.
			if (!console[method]) {
				console[method] = noop;
			}
		}
	})();

})();
<commit_msg>fix typo<commit_after>/**
 * @file  Base JavaScript needed independent of the project
 * @author Michael Kühnel <m.kuehnel@micromata.de>
 */

/**
 * The jQuery object or a jQuery set containing on or more DOM elements.
 * @typedef {Object} jQuery
 */

/**
 * Namespace of the app.
 * This should be the only variable the app stores in the global scope.
 * @namespace kickstarter
 * @todo Rename the global variable throughout the whole file according to your needs.
 * @example
 * yourAppName = window.yourAppName || {};
 * @todo Update documentation after renaming.
 */
var kickstarter = window.kickstarter || {};

/**
 * Namespace of the base module. Contains JavaScript needed independent of the
 * project or other modules.
 * @namespace base
 * @memberof kickstarter
 */
kickstarter.base = (function () {
	'use strict';

	/**
	 * Fix viewport issues with IE 10.
	 * @see {@link http://getbootstrap.com/getting-started/#support-ie10-width}
	 * @memberof kickstarter.base
	 * @private
	 */
	(function () {
		if (navigator.userAgent.match(/IEMobile\/10\.0/)) {
			var msViewportStyle = document.createElement('style');
			msViewportStyle.appendChild(
				document.createTextNode(
					'@-ms-viewport{width:auto!important}'
				)
			);
			document.querySelector('head').appendChild(msViewportStyle);
		}
	})();

	/**
	 * Avoid `console` errors in browsers that lack a console.
	 * @see {@link https://github.com/h5bp/html5-boilerplate/blob/master/js/plugins.js}
	 * @memberof kickstarter.base
	 * @private
	 */
	(function () {
		var method;
		var noop = function () {};
		var methods = [
			'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
			'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
			'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
			'timeStamp', 'trace', 'warn'
		];
		var length = methods.length;
		var console = (window.console = window.console || {});

		while (length--) {
			method = methods[length];

			// Only stub undefined methods.
			if (!console[method]) {
				console[method] = noop;
			}
		}
	})();

})();
<|endoftext|>


<commit_before>function changeLang() {
    var addr = $(location).attr('href');

    if(addr.indexOf('fr') > -1){
        addr = addr.replace('fr','en');
    }
    else if(addr.indexOf('en') > -1){
        addr = addr.replace('en','fr');
    }

    window.location = addr;
}

<commit_msg>refined change language script<commit_after>function changeLang() {
    var addr = $(location).attr('href');

    if(addr.indexOf('/fr/') > -1){
        addr = addr.replace('/fr/','/en/');
    }
    else if(addr.indexOf('/en/') > -1){
        addr = addr.replace('/en/','/fr/');
    }

    window.location = addr;
}

<|endoftext|>


<commit_before>// default values
var current_octave = 2;
var sound_volume = 1;
var keyboard_control = true;
var piano_key_count = 0;
var selected_key = null;
var piano_octave_count = 5;
var offset_scale = [];
var offset_chords = [];
var scale_color = 'green';
var sound = [];

function piano_init()
{
	var octave = 0;
	// this is where we generate the piano
	for (x = 0; x <= piano_octave_count; x++) {
		// a template of 1 octaves worth of keys
		var template = '

';
		octave = octave + 12;
		// generate the whole piano and append it to the piano id
		$('#piano').append(template);
		piano_key_count = octave;
	}
}

function click_play_init()
{
	$("#piano div li div, #piano div li span").on("mousedown", function(){click_play(this)}); // click the notes to play them
}

function load_audio_samples()
{
	for (index = 0; index <= piano_key_count; index++)
	{
		sound[index] = new Howl({
	  		urls: ['assets/sound/'+index+'.mp3', 'assets/sound/'+index+'.ogg', 'assets/sound/'+index+'.wav'],
	  		buffer: true
		});
	}
}

function volume_slider_init()
{
	// set the sound volume value according to the slider
	$('.range-slider').on('change', function(){
		var slider_value = $('.range-slider').attr('data-slider');
		// set the sound volume value to a number between 0 and 1
		sound_volume = (slider_value / 100);
		console.log(sound_volume);
	});
}

function set_scale_color()
{
	// get the selected scale color
	scale_color = $('#color_list option:selected').val();
	// apply custom colors to the scale dot
	$('.scale_dot').css({'background-color':scale_color})
}

function click_play(obj_this)
{
	var this_index = $(obj_this).attr('index');
	play_multi_sound(this_index);
}

function get_keys()
{
	var key_list = [];
	$(notes).each(function(key,value)
	{
		var in_array = $.inArray(value.key,key_list);
		// add the new key to the list
		if (in_array == -1)
		{
			$('#key_list').append('<option value="'+value.offset+'" type="'+value.type+'">'+value.key+'</option');
			key_list.push(value.key);
		}
	});
}

function set_key()
{
	selected_key = parseInt($('#key_list').find(":selected").val(), 10);
	set_scale();
}

function get_scale()
{
	var disabled = ['melodic','harmonic','lydian','mixo'];
	$(scales).each(function(key,value)
	{
		if ($.inArray(value.type, disabled) > -1)
		{
			$('#scale_list').append('<option value="'+value.type+'" disabled>'+value.name+'</option');
		}
		else
		{
			$('#scale_list').append('<option value="'+value.type+'">'+value.name+'</option');
		}
	});
}

function set_scale()
{
	var selected_scale = $('#scale_list option:selected').val();
	// make sure the user actually selects a key
	if (selected_key != null)
	{
		// clear all dots before we select a scale
		$('.scale_dot').remove();
		// create a fresh array, straight out of the oven
		var dot_scale = [];
		// loop over all of our available scales
		$(scales).each(function(key,value)
		{
			// match our selected scale to our available ones
			if (value.type === selected_scale)
			{
				/*
				we want to start the octave_increment at a -12 because the scale offsets start at a positive number greater than  0 (C key; which is our lowest key value)
				since scales after C are always greater than 0, we still want to fill in any notes behind our root note value, therefore we will increment an extra time in
				our FOR loop to compensate for this (thats why x = -1)
				*/
				var octave_increment = -12;
				for (x = -1; x <= piano_octave_count; x++)
				{
					// loop over all of our offsets, add our selected key offset values and then stuff them into an array
					$(value.scale_offset).each(function(key,value)
					{
						// create a new scale with the appropriate scale and key offsets
						dot_scale.push((value + selected_key) + octave_increment);
					});
					// we need to increment 12 notes each iteration in order to process the whole scale
					octave_increment = octave_increment + 12;
				}
			}
			// loop over our dot value and place them on the scale!
			$(dot_scale).each(function(key, value)
			{
				// apply the dots!
				$('#piano').find("[index|='"+value+"']").html('

'); // comma allows us to use either the key or the alt-key values if available
				// set the scale color!
				set_scale_color();
			});
		});
	}
	else
	{
		return false;
	}
}

function set_octave()
{
	// set the octave select list to the default value
	var octave_list_value = $('#octave_list :selected').index();
	// set the current octave to the newly selected value
	current_octave = octave_list_value;
}

function toggle_keyboard()
{
	keyboard_control = $('#keyboard_checkbox').is(':checked');

	if (keyboard_control == false)
	{
		$('#octave_list').prop('disabled', true);
	}
	else
	{
		$('#octave_list').prop('disabled', false);
	}
}

function play_multi_sound(id)
{
	// play the selected sound
	sound[id].volume(sound_volume).play();
}

function set_fifths()
{
	var key_type = $('#key_list option:selected').attr('type');
	var key_value = $('#key_list option:selected').text();
	var scale_type = $('#scale_list option:selected').val();
	var octave_offset_scale = [];
	var offset_scale_keys = [];

	// make sure we have values
	if (key_type && scale_type)
	{
		$(scales).each(function(key, value)
		{
			if (value.type === scale_type)
			{

				// reset the offset scale
				offset_scale = [];

				// create the offset scale!
				$(value.scale_offset).each(function(key,value)
				{
					offset_scale.push(value + selected_key);
					octave_offset_scale.push(value + selected_key + (current_octave * 12));
				});

				// set the roman numeral values!
				$(numerals).each(function(key,value)
				{
					if (value.type === scale_type)
					{
						$(value.numerals).each(function(key,value)
						{
							$('#numeral_'+ key).text(value);
						});
					}
				});
			}
		});

		var key_list = {};
		var note_list = {};
		var key_progression = ['C','D','E','F','G','A','B'];

		//console.log(offset_scale);

		// find the starting point in our array based off the selected key
		var key_index = key_progression.indexOf(key_value.substring(0,1));

		// we loop over our new offset scale
		$(offset_scale).each(function(k,v)
		{
			// then we loop over each note and match up the new offset scale values to the offset values in the notes array
			$(notes).each(function(key, value)
			{
				if (v == value.offset && key_progression[key_index] == value.key.substring(0,1) && note_list[value.key.substring(0,1)] == null 	&& key_list[value.offset] == null)
				{
					// if the key index value ever becomes greater than the length of the progression array then we must reset the value
					if (key_index >= (key_progression.length - 1))
					{
						key_index = -1;
					}

					if (key_index <= key_progression.length)
					{
						offset_scale_keys.push(value.key);
					}
					key_list[value.offset] = true;
					note_list[value.key.substring(0,1)] = true;
					key_index++;
				}
			});
		});

		// display the offset_scale_keys
		$(offset_scale_keys).each(function(key,value)
		{
			$('#chord_' + key).text(value);
		});

		//console.log(offset_scale_keys);

	}
	else
	{
		// clear the chord text
		$("[id^='chord_']").text('-');
		// dont do anything
		return false;
	}
}

function get_chords()
{
	var offset_notes = [];
	var chord_circle = [];
	var chord_list = [];
	var scale_type = $('#scale_list option:selected').val();
	var key_type = $('#key_list option:selected').attr('type');

	if (scale_type && key_type)
	{
		// determine which fifths chord template to use
		$(fifths).each(function(key,value)
		{
			if (value.type == scale_type)
			{
				chord_circle = value.circle;
			}
		});

		// generate the key offsets from the offset scale
		$(offset_scale).each(function(key, value)
		{
			offset_notes.push(value + (current_octave * 12));
		});

	 	// generate an array of chords based on our provided key list and circle of fifths
		$(chord_circle).each(function(key,value)
		{
			$(chords).each(function(k,v)
			{
				if (value == v.type)
				{
					chord_list.push(v.chord);
				}
			});
		});

		// reset the offset_chords array
		offset_chords = [];

		// apply the scale offset to each chord
		for (i = 0; i < chord_list.length; i++)
		{
			var new_chord = [];
			// executes 3 times
			$(chord_list[i]).each(function(key,value)
			{
				new_chord.push(value + offset_notes[i]);
			});
			// add our newly created chords to the offset_chords array
			offset_chords.push(new_chord);
		}
	}
	else
	{
		return false;
	}
}

function chords_init()
{
	// highlight the offset chords on the piano
	$("[id^='chord_']").hover(
	  	// mouse enters
	  	function() {
	  		var this_val = $(this).attr('value');
	  		// highlight the chord!
	  		$(offset_chords[this_val]).each(function(key,value)
	  		{
	  			$("[index|='"+value+"']").addClass('chord_selected');
	  		});
		},
		// mouse leaves
		function() {
			$("#piano li div").removeClass('chord_selected');
			$("#piano li span").removeClass('chord_selected');
	  	}
	);

	// highlight the offset chords on the piano
	$("[id^='chord_']").on({
		"mousedown" : function(e)
		{
			var this_val = $(this).attr('value');
	  		// highlight the chord!
	  		$(offset_chords[this_val]).each(function(key,value)
	  		{
	  			$("[index|='"+value+"']").addClass('chord_clicked');
	  			play_multi_sound(value);
	  		});
		},
		"mouseup" : function()
		{
			$("#piano li div").removeClass('chord_clicked');
			$("#piano li span").removeClass('chord_clicked');
		}
	});
}<commit_msg>Sound fix<commit_after>// default values
var current_octave = 2;
var sound_volume = 1;
var keyboard_control = true;
var piano_key_count = 0;
var selected_key = null;
var piano_octave_count = 5;
var offset_scale = [];
var offset_chords = [];
var scale_color = 'green';
var sound = [];

function piano_init()
{
	var octave = 0;
	// this is where we generate the piano
	for (x = 0; x <= piano_octave_count; x++) {
		// a template of 1 octaves worth of keys
		var template = '

';
		octave = octave + 12;
		// generate the whole piano and append it to the piano id
		$('#piano').append(template);
		piano_key_count = octave;
	}
}

function click_play_init()
{
	$("#piano div li div, #piano div li span").on("mousedown", function(){click_play(this)}); // click the notes to play them
}

function load_audio_samples()
{
	for (index = 0; index <= piano_key_count; index++)
	{
		sound[index] = new Howl({
	  		urls: ['assets/sound/'+index+'.mp3', 'assets/sound/'+index+'.ogg', 'assets/sound/'+index+'.wav'],
	  		buffer: true
		});
	}
}

function volume_slider_init()
{
	// set the sound volume value according to the slider
	$('.range-slider').on('change', function(){
		var slider_value = $('.range-slider').attr('data-slider');
		// set the sound volume value to a number between 0 and 1
		sound_volume = (slider_value / 100);
	});
}

function set_scale_color()
{
	// get the selected scale color
	scale_color = $('#color_list option:selected').val();
	// apply custom colors to the scale dot
	$('.scale_dot').css({'background-color':scale_color})
}

function click_play(obj_this)
{
	var this_index = $(obj_this).attr('index');
	play_multi_sound(this_index);
}

function get_keys()
{
	var key_list = [];
	$(notes).each(function(key,value)
	{
		var in_array = $.inArray(value.key,key_list);
		// add the new key to the list
		if (in_array == -1)
		{
			$('#key_list').append('<option value="'+value.offset+'" type="'+value.type+'">'+value.key+'</option');
			key_list.push(value.key);
		}
	});
}

function set_key()
{
	selected_key = parseInt($('#key_list').find(":selected").val(), 10);
	set_scale();
}

function get_scale()
{
	var disabled = ['melodic','harmonic','lydian','mixo'];
	$(scales).each(function(key,value)
	{
		if ($.inArray(value.type, disabled) > -1)
		{
			$('#scale_list').append('<option value="'+value.type+'" disabled>'+value.name+'</option');
		}
		else
		{
			$('#scale_list').append('<option value="'+value.type+'">'+value.name+'</option');
		}
	});
}

function set_scale()
{
	var selected_scale = $('#scale_list option:selected').val();
	// make sure the user actually selects a key
	if (selected_key != null)
	{
		// clear all dots before we select a scale
		$('.scale_dot').remove();
		// create a fresh array, straight out of the oven
		var dot_scale = [];
		// loop over all of our available scales
		$(scales).each(function(key,value)
		{
			// match our selected scale to our available ones
			if (value.type === selected_scale)
			{
				/*
				we want to start the octave_increment at a -12 because the scale offsets start at a positive number greater than  0 (C key; which is our lowest key value)
				since scales after C are always greater than 0, we still want to fill in any notes behind our root note value, therefore we will increment an extra time in
				our FOR loop to compensate for this (thats why x = -1)
				*/
				var octave_increment = -12;
				for (x = -1; x <= piano_octave_count; x++)
				{
					// loop over all of our offsets, add our selected key offset values and then stuff them into an array
					$(value.scale_offset).each(function(key,value)
					{
						// create a new scale with the appropriate scale and key offsets
						dot_scale.push((value + selected_key) + octave_increment);
					});
					// we need to increment 12 notes each iteration in order to process the whole scale
					octave_increment = octave_increment + 12;
				}
			}
			// loop over our dot value and place them on the scale!
			$(dot_scale).each(function(key, value)
			{
				// apply the dots!
				$('#piano').find("[index|='"+value+"']").html('

'); // comma allows us to use either the key or the alt-key values if available
				// set the scale color!
				set_scale_color();
			});
		});
	}
	else
	{
		return false;
	}
}

function set_octave()
{
	// set the octave select list to the default value
	var octave_list_value = $('#octave_list :selected').index();
	// set the current octave to the newly selected value
	current_octave = octave_list_value;
}

function toggle_keyboard()
{
	keyboard_control = $('#keyboard_checkbox').is(':checked');

	if (keyboard_control == false)
	{
		$('#octave_list').prop('disabled', true);
	}
	else
	{
		$('#octave_list').prop('disabled', false);
	}
}

function play_multi_sound(id)
{
	// play the selected sound
	sound[id].volume(sound_volume).play();
}

function set_fifths()
{
	var key_type = $('#key_list option:selected').attr('type');
	var key_value = $('#key_list option:selected').text();
	var scale_type = $('#scale_list option:selected').val();
	var octave_offset_scale = [];
	var offset_scale_keys = [];

	// make sure we have values
	if (key_type && scale_type)
	{
		$(scales).each(function(key, value)
		{
			if (value.type === scale_type)
			{

				// reset the offset scale
				offset_scale = [];

				// create the offset scale!
				$(value.scale_offset).each(function(key,value)
				{
					offset_scale.push(value + selected_key);
					octave_offset_scale.push(value + selected_key + (current_octave * 12));
				});

				// set the roman numeral values!
				$(numerals).each(function(key,value)
				{
					if (value.type === scale_type)
					{
						$(value.numerals).each(function(key,value)
						{
							$('#numeral_'+ key).text(value);
						});
					}
				});
			}
		});

		var key_list = {};
		var note_list = {};
		var key_progression = ['C','D','E','F','G','A','B'];

		//console.log(offset_scale);

		// find the starting point in our array based off the selected key
		var key_index = key_progression.indexOf(key_value.substring(0,1));

		// we loop over our new offset scale
		$(offset_scale).each(function(k,v)
		{
			// then we loop over each note and match up the new offset scale values to the offset values in the notes array
			$(notes).each(function(key, value)
			{
				if (v == value.offset && key_progression[key_index] == value.key.substring(0,1) && note_list[value.key.substring(0,1)] == null 	&& key_list[value.offset] == null)
				{
					// if the key index value ever becomes greater than the length of the progression array then we must reset the value
					if (key_index >= (key_progression.length - 1))
					{
						key_index = -1;
					}

					if (key_index <= key_progression.length)
					{
						offset_scale_keys.push(value.key);
					}
					key_list[value.offset] = true;
					note_list[value.key.substring(0,1)] = true;
					key_index++;
				}
			});
		});

		// display the offset_scale_keys
		$(offset_scale_keys).each(function(key,value)
		{
			$('#chord_' + key).text(value);
		});

		//console.log(offset_scale_keys);

	}
	else
	{
		// clear the chord text
		$("[id^='chord_']").text('-');
		// dont do anything
		return false;
	}
}

function get_chords()
{
	var offset_notes = [];
	var chord_circle = [];
	var chord_list = [];
	var scale_type = $('#scale_list option:selected').val();
	var key_type = $('#key_list option:selected').attr('type');

	if (scale_type && key_type)
	{
		// determine which fifths chord template to use
		$(fifths).each(function(key,value)
		{
			if (value.type == scale_type)
			{
				chord_circle = value.circle;
			}
		});

		// generate the key offsets from the offset scale
		$(offset_scale).each(function(key, value)
		{
			offset_notes.push(value + (current_octave * 12));
		});

	 	// generate an array of chords based on our provided key list and circle of fifths
		$(chord_circle).each(function(key,value)
		{
			$(chords).each(function(k,v)
			{
				if (value == v.type)
				{
					chord_list.push(v.chord);
				}
			});
		});

		// reset the offset_chords array
		offset_chords = [];

		// apply the scale offset to each chord
		for (i = 0; i < chord_list.length; i++)
		{
			var new_chord = [];
			// executes 3 times
			$(chord_list[i]).each(function(key,value)
			{
				new_chord.push(value + offset_notes[i]);
			});
			// add our newly created chords to the offset_chords array
			offset_chords.push(new_chord);
		}
	}
	else
	{
		return false;
	}
}

function chords_init()
{
	// highlight the offset chords on the piano
	$("[id^='chord_']").hover(
	  	// mouse enters
	  	function() {
	  		var this_val = $(this).attr('value');
	  		// highlight the chord!
	  		$(offset_chords[this_val]).each(function(key,value)
	  		{
	  			$("[index|='"+value+"']").addClass('chord_selected');
	  		});
		},
		// mouse leaves
		function() {
			$("#piano li div").removeClass('chord_selected');
			$("#piano li span").removeClass('chord_selected');
	  	}
	);

	// highlight the offset chords on the piano
	$("[id^='chord_']").on({
		"mousedown" : function(e)
		{
			var this_val = $(this).attr('value');
	  		// highlight the chord!
	  		$(offset_chords[this_val]).each(function(key,value)
	  		{
	  			$("[index|='"+value+"']").addClass('chord_clicked');
	  			play_multi_sound(value);
	  		});
		},
		"mouseup" : function()
		{
			$("#piano li div").removeClass('chord_clicked');
			$("#piano li span").removeClass('chord_clicked');
		}
	});
}<|endoftext|>


<commit_before>const baseUrl = window.location.origin === "http://task.payakapps.com" ? "http://task.payakapps.com/" : "http://localhost/task/";

var userId = null;
var authorId = null;
var taskType = null;
var userName = null;
var avatarUrl = null;

function setAuthorId(id) { authorId = id; }

function getAuthorId() { return authorId; }

function setTaskType(type) { taskType = type; }

function getTaskType() { return taskType; }

function setUserId(id) { userId = id; }

function getUserId() { return userId; }

// Task Builder
function taskBuilder(task, actorIcon = true, modalDismiss = false) {

    var taskString          = "";
    var actorsAppend        = "";
    var contributorsAppend  = "";
    var iconAppend          = "";
    var modalDismissAppend  = modalDismiss ? 'data-dismiss="modal"': '';

    if (actorIcon) {

        var actors      = task['actors'];
        actorsAppend    = "Contributor
";

        if(actors.length) {

            actors.forEach(function(actor) {

                actorsAppend += actor['first_name'] + " " + actor['last_name'] + "
";
            });
        } else {

            actorsAppend = "No Contributor";
        }

        contributorsAppend = `data-toggle="popover" data-trigger="hover" data-html="true" data-placement="right" data-content="${actorsAppend}"`;

        if (actors.length == 0) {

            iconAppend = ' ';
        } else if (actors.length == 1) {

            iconAppend = ' ';
        } else if (actors.length > 1) {

            iconAppend = ' ';
        }
    }

    var taskString =
    `

${iconAppend + task['title']}

`;

    return taskString;
}

// Column Builder
function columnBuilder(column) {
    var columnString =
    `

                ${column['name']}

            <button type="button" class="btn btn-primary btn-lg btn-block my-2 task-create"
                data-toggle="modal" data-target="#taskModifyModal" data-parent="${column['id']}">
                 Add Task
            </button>

`;

    return columnString;
}

// Team
function displayMember(items, edit = false) {

    items.forEach(function(item) {

        if(edit) {

            $('.team-member').before(
                `<span class="badge badge-dark mx-1">${item['first_name']} ${item['last_name']} <a class="team-member-remove" data-value="${item['email_address']}">×</a></span>`
            );

            $('#teamModifyModal form').append(
                `<input type="hidden" name="members[]" value="${item['email_address']}" />`
            );
        } else

            $('.team-member-list').append(
                `<span class="badge badge-secondary">${item['first_name']} ${item['last_name']}</span>`
            );
    });
};

// Task
function resetForm() {

    $('#taskModifyModal').find('form')[0].reset();

    $('#taskModifyModal form').children('input').remove();
    $('#taskModifyModal').find('.task-tag').siblings('span.badge').remove();
    $('#taskModifyModal').find('.task-actor').siblings('span.badge').remove();
    $('#taskModifyModal').find('.btn-color').find('i').removeClass('fa fa-check fa-lg');
    $('#taskModifyModal').find(`button[data-value="#ffffff"] i`).addClass('fa fa-check fa-lg');
    $('#taskModifyModal .card').css('background-color', '#ffffff');
};

function displayTask(items) {

    $('.kanban-column .task-create').prevAll().remove();

    items.forEach(function(item) {

        $(`.kanban-column[data-value="${item['column_id']}"]>.card-body`).prepend(taskBuilder(item, getTaskType() == 'team'));
    });
};

function displayActor(items, edit = false) {

    items.forEach(function(item) {

        if(edit) {

            $('#taskModifyModal .task-actor').before(
                `<span class="badge badge-dark mx-1">${item['first_name'] + ' ' + item['last_name']} <a class="task-actor-remove" data-value="${item['email_address']}">×</a></span>`
            );

            $('#taskModifyModal form').append(
                `<input type="hidden" name="actors[]" value="${item['email_address']}" />`
            );
        } else {

            $('.task-actor-list').append(
                `<span class="badge badge-dark mx-1">${item['first_name'] + ' ' + item['last_name']}</span>`
            );
        }
    });
};

function displayTag(items, edit = false) {

    items.forEach(function(item) {

        if(edit) {

            $('#taskModifyModal .task-tag').before(
                `<span class="badge badge-dark badge-pill mx-1">${item['name']} <a class="task-tag-remove" data-value="${item['name']}">×</a></span>`
            );
            $('#taskModifyModal .task-tag').append(
                `<input type="hidden" name="tags[]" value="${item['name']}" />`
            );
        } else {

            $('.task-tag-list').append(
                `<span class="badge badge-dark badge-pill mx-1">${item['name']}</span>`
            );
        }
    });
};

function displayNote(items) {

    items.forEach(function(item){

        var user = getUser(item['user_id']).responseJSON;

        $('.task-note-list').append(
            `

                ${item['body']}

`
        );
    });
}

function searchTask(items, keyword) {

    $('#taskSearchList').html('');

    if(keyword != '') {

        items.forEach(function(item) {

            if(item['title'].toLowerCase().indexOf(keyword.toLowerCase()) != -1) {

                $('#taskSearchList').append(taskBuilder(item, getTaskType() == 'team', true));
            }
        });
    }
};

// Kanban
function displayBoard(board) {

    $('#kanbanBoard .card-group').html('');

    $('#kanbanBoard .card-group').css(`width`, `${(board['columns'].length + 1) * 25}%`);

    board['columns'].forEach(function(column) {

        $('#kanbanBoard .card-group').append(columnBuilder(column));
    });

    $('#kanbanBoard .card-group').append(`

                Type Here

    `);
};

function addColumn(column) {

    $('#kanbanBoard .card-group').css(`width`,  `${(column['position'] + 1) * 25}%`);

    $('#addColumn').before(columnBuilder(column));
};

function highlightTask() {

    userId = getUserId();

    getActorTask(userId).done(function (data) {

        data.forEach(function(item) {

            $(`#kanbanBoard .kanban-task[data-value="${item['id']}"]`).toggleClass('active');
        });
    }).always(function () {

        $('#kanbanBoard').toggleClass('highlight');
    });
};  <commit_msg>fixed base url mainjs<commit_after>switch(window.location.origin){
    case 'http://task.payakapps.com': const baseUrl = 'http://task.payakapps.com/'; break;
    case 'http://stage.payakapps.com': const baseUrl = 'http://stage.payakapps.com/'; break;
    default: const baseUrl = 'http://task.payakapps.com/'; break;
}

var userId = null;
var authorId = null;
var taskType = null;
var userName = null;
var avatarUrl = null;

function setAuthorId(id) { authorId = id; }

function getAuthorId() { return authorId; }

function setTaskType(type) { taskType = type; }

function getTaskType() { return taskType; }

function setUserId(id) { userId = id; }

function getUserId() { return userId; }

// Task Builder
function taskBuilder(task, actorIcon = true, modalDismiss = false) {

    var taskString          = "";
    var actorsAppend        = "";
    var contributorsAppend  = "";
    var iconAppend          = "";
    var modalDismissAppend  = modalDismiss ? 'data-dismiss="modal"': '';

    if (actorIcon) {

        var actors      = task['actors'];
        actorsAppend    = "Contributor
";

        if(actors.length) {

            actors.forEach(function(actor) {

                actorsAppend += actor['first_name'] + " " + actor['last_name'] + "
";
            });
        } else {

            actorsAppend = "No Contributor";
        }

        contributorsAppend = `data-toggle="popover" data-trigger="hover" data-html="true" data-placement="right" data-content="${actorsAppend}"`;

        if (actors.length == 0) {

            iconAppend = ' ';
        } else if (actors.length == 1) {

            iconAppend = ' ';
        } else if (actors.length > 1) {

            iconAppend = ' ';
        }
    }

    var taskString =
    `

${iconAppend + task['title']}

`;

    return taskString;
}

// Column Builder
function columnBuilder(column) {
    var columnString =
    `

                ${column['name']}

            <button type="button" class="btn btn-primary btn-lg btn-block my-2 task-create"
                data-toggle="modal" data-target="#taskModifyModal" data-parent="${column['id']}">
                 Add Task
            </button>

`;

    return columnString;
}

// Team
function displayMember(items, edit = false) {

    items.forEach(function(item) {

        if(edit) {

            $('.team-member').before(
                `<span class="badge badge-dark mx-1">${item['first_name']} ${item['last_name']} <a class="team-member-remove" data-value="${item['email_address']}">×</a></span>`
            );

            $('#teamModifyModal form').append(
                `<input type="hidden" name="members[]" value="${item['email_address']}" />`
            );
        } else

            $('.team-member-list').append(
                `<span class="badge badge-secondary">${item['first_name']} ${item['last_name']}</span>`
            );
    });
};

// Task
function resetForm() {

    $('#taskModifyModal').find('form')[0].reset();

    $('#taskModifyModal form').children('input').remove();
    $('#taskModifyModal').find('.task-tag').siblings('span.badge').remove();
    $('#taskModifyModal').find('.task-actor').siblings('span.badge').remove();
    $('#taskModifyModal').find('.btn-color').find('i').removeClass('fa fa-check fa-lg');
    $('#taskModifyModal').find(`button[data-value="#ffffff"] i`).addClass('fa fa-check fa-lg');
    $('#taskModifyModal .card').css('background-color', '#ffffff');
};

function displayTask(items) {

    $('.kanban-column .task-create').prevAll().remove();

    items.forEach(function(item) {

        $(`.kanban-column[data-value="${item['column_id']}"]>.card-body`).prepend(taskBuilder(item, getTaskType() == 'team'));
    });
};

function displayActor(items, edit = false) {

    items.forEach(function(item) {

        if(edit) {

            $('#taskModifyModal .task-actor').before(
                `<span class="badge badge-dark mx-1">${item['first_name'] + ' ' + item['last_name']} <a class="task-actor-remove" data-value="${item['email_address']}">×</a></span>`
            );

            $('#taskModifyModal form').append(
                `<input type="hidden" name="actors[]" value="${item['email_address']}" />`
            );
        } else {

            $('.task-actor-list').append(
                `<span class="badge badge-dark mx-1">${item['first_name'] + ' ' + item['last_name']}</span>`
            );
        }
    });
};

function displayTag(items, edit = false) {

    items.forEach(function(item) {

        if(edit) {

            $('#taskModifyModal .task-tag').before(
                `<span class="badge badge-dark badge-pill mx-1">${item['name']} <a class="task-tag-remove" data-value="${item['name']}">×</a></span>`
            );
            $('#taskModifyModal .task-tag').append(
                `<input type="hidden" name="tags[]" value="${item['name']}" />`
            );
        } else {

            $('.task-tag-list').append(
                `<span class="badge badge-dark badge-pill mx-1">${item['name']}</span>`
            );
        }
    });
};

function displayNote(items) {

    items.forEach(function(item){

        var user = getUser(item['user_id']).responseJSON;

        $('.task-note-list').append(
            `

                ${item['body']}

`
        );
    });
}

function searchTask(items, keyword) {

    $('#taskSearchList').html('');

    if(keyword != '') {

        items.forEach(function(item) {

            if(item['title'].toLowerCase().indexOf(keyword.toLowerCase()) != -1) {

                $('#taskSearchList').append(taskBuilder(item, getTaskType() == 'team', true));
            }
        });
    }
};

// Kanban
function displayBoard(board) {

    $('#kanbanBoard .card-group').html('');

    $('#kanbanBoard .card-group').css(`width`, `${(board['columns'].length + 1) * 25}%`);

    board['columns'].forEach(function(column) {

        $('#kanbanBoard .card-group').append(columnBuilder(column));
    });

    $('#kanbanBoard .card-group').append(`

                Type Here

    `);
};

function addColumn(column) {

    $('#kanbanBoard .card-group').css(`width`,  `${(column['position'] + 1) * 25}%`);

    $('#addColumn').before(columnBuilder(column));
};

function highlightTask() {

    userId = getUserId();

    getActorTask(userId).done(function (data) {

        data.forEach(function(item) {

            $(`#kanbanBoard .kanban-task[data-value="${item['id']}"]`).toggleClass('active');
        });
    }).always(function () {

        $('#kanbanBoard').toggleClass('highlight');
    });
};  <|endoftext|>