<commit_before>window.hwView = {
	subjects: {},
	loadCount: 0,
	swapOrder: false
};

window.hwView.addEventToList = function(ev, list) {
	var tag = window.utils.getPrefix(ev.name);
	var name = ev.name.split(" ");
	name.splice(0, 1);
	name = name.join(" ");
	var done = (ev.done == 1 ? true : false);

	if (name.trim() == "") {
		return;
	}

	var $item = $('

');
		if (done) {
			$item.addClass("hwView-done");
		}
		var $name = $('

');
			$name.text(name);
		// append comes later

		var $lineTwo = $('

');
			var $tag = $('');
				$tag.addClass("first-word");
				if (tag.toLowerCase() == "read") {
					tag = tag + "ing";
				}
				if(window.hwView.clr) {
					$tag.css("color", "black");
				} else {
					$tag.addClass(window.utils.getPrefixClass(tag));
				}

				$tag.text(tag);
			$lineTwo.append($tag);

			var $subject = $('');
				$subject.text(" in " + window.hwView.subjects[ev.subject].name);
			$lineTwo.append($subject);

			var $due = $('');
				var keyword = "due";
				if (tag.toLowerCase() == "test" || tag.toLowerCase() == "exam" || tag.toLowerCase() == "midterm" || tag.toLowerCase() == "quiz" || tag.toLowerCase() == "ica" || tag.toLowerCase() == "lab") {
					keyword = "on";
				}
				var dueText = window.utils.formatDate_pretty(moment(ev.due).add(1, "day").toDate());
				if (moment(ev.due).add(1, "day").week() == moment().week()) {
					dueText = window.utils.getDayOfWeek(moment(ev.due).add(1, "day").day());
				}
				$due.text(" " + keyword + " " + dueText);
			$lineTwo.append($due);
		// append comes later

		if (window.hwView.swapOrder) {
			$item.append($lineTwo);
			$item.append($name);
		} else {
			$item.append($name);
			$item.append($lineTwo);
		}

	$(".hwView-" + list + " ul").append($item);
};

window.hwView.loadList = function(date, list, callback) {
	for (var subjectIndex in window.hwView.subjects) {
		window.api.get("planner/events/get/" + window.utils.formatDate_api(date) + "/" + subjectIndex, function(data) {
			var ev = data.events;
			window.hwView.loadStep();
			if (ev.length == 0) {
				return;
			}
			for (var evIndex in ev) {
				var evObj = {
					name: ev[evIndex].text,
					due: new Date(data.date),
					subject: ev[evIndex].sectionIndex,
					done: ev[evIndex].done
				};
				window.hwView.addEventToList(evObj, list);
			};
		});
	}
};

window.hwView.loadSubjects = function(callback) {
	window.hwView.subjects = {};
	window.api.get("planner/sections/get/", function(data) {
		for (var i = 0; i < data.sections.length; i++) {
			var itm = data.sections[i];
			window.hwView.subjects[itm.sectionIndex] = itm;
		};
		callback(data.sections);
	});
};

window.hwView.findNextDay = function(offset) {
	var retVal = moment(); //moment().add("days", offset);
	if (retVal.day() == 6) {
		// don't start on Saturday
		retVal.add(1, "day");
	}
	for (var i = 0; i < offset; i++) {
		retVal.add(1, "day"); //add a day
		// is it a Saturday?
		if (retVal.day() == 6) {
			retVal.add(2, "day"); // skip the weekend
		}
	}
	return retVal.toDate();
};

window.hwView.loadStep = function() {
	window.hwView.loadCount += 1;
	if (window.hwView.loadCount >= 2) { //(1 + (14 * Object.keys(window.hwView.subjects).length))) {
		window.page.hideLoading();
		if ($(".hwView-tomorrow ul").text() == "" && $(".hwView-soon ul").text() == "" && $(".hwView-longterm ul").text() == "") {
			// empty!
			swal("You don't have any homework in your planner!", " Once you've written some assignments in, come back here.", "warning")
		}
	}
};

window.hwView.loadEvents = function(callback) {
	window.hwView.loadSubjects(function(data) {
		window.hwView.loadStep();
		window.api.get("hwView/getHw?date=" + moment().format('YYYY-MM-DD'), function(data) {
			var ev = data.events;
			if (ev.length == 0) {
				window.hwView.loadStep();
				return;
			}
			for (var evIndex in ev) {
				var evObj = {
					name: ev[evIndex].text,
					due: new Date(ev[evIndex].date.split("T")[0]),
					subject: ev[evIndex].sectionIndex,
					done: ev[evIndex].done
				};
				var list = "longterm";
				var dueMoment = moment(evObj.due).utcOffset(0);
				var tomorrow = moment(window.hwView.findNextDay(1)).date();
				if (dueMoment.date() == tomorrow) {
					list = "tomorrow";
				} else if (dueMoment.isBefore(moment(window.hwView.findNextDay(5)).subtract(1, "day"))) {
					list = "soon";
				}
				window.hwView.addEventToList(evObj, list);
			};
			window.hwView.loadStep();
		});
		/*window.hwView.loadStep();
		window.hwView.loadList(window.hwView.findNextDay(1), "tomorrow", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(2), "soon", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(3), "soon", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(4), "soon", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(5), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(6), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(7), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(8), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(9), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(10), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(11), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(12), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(13), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(14), "longterm", function() {

		});*/
	});
	callback();
};

$(document).ready(function() {
	$("#hwView").on("tabOpened", function() {
		window.page.showLoading();
		window.hwView.loadCount = 0;
		// clear lists
		$(".hwView-tomorrow ul").text("");
		$(".hwView-soon ul").text("");
		$(".hwView-longterm ul").text("");
		// special case for checking on Friday, Saturday, or Sunday
		if (moment().day() == 0 || moment().day() == 5 || moment().day() == 6) {
			$(".tomorrow-or-monday").text("Monday"); // the part that makes it show Monday stuff is in findNextDay().
		} else {
			$(".tomorrow-or-monday").text("tomorrow");
		}
		window.prefs.get("name-subj", function(val) {
			window.hwView.swapOrder = (val == "1");
			window.prefs.get("titleclr", function(val) {
				window.hwView.clr = (val == "1");
				window.hwView.loadEvents(function() {
				});
			});

		});
	});
});
<commit_msg>Close #223 maybe<commit_after>window.hwView = {
	subjects: {},
	loadCount: 0,
	swapOrder: false
};

window.hwView.addEventToList = function(ev, list) {
	var tag = window.utils.getPrefix(ev.name);
	var name = ev.name.split(" ");
	name.splice(0, 1);
	name = name.join(" ");
	var done = (ev.done == 1 ? true : false);

	if (name.trim() == "") {
		return;
	}

	var $item = $('

');
		if (done) {
			$item.addClass("hwView-done");
		}
		var $name = $('

');
			$name.text(name);
		// append comes later

		var $lineTwo = $('

');
			var $tag = $('');
				$tag.addClass("first-word");
				if (tag.toLowerCase() == "read") {
					tag = tag + "ing";
				}
				if(window.hwView.clr) {
					$tag.css("color", "black");
				} else {
					$tag.addClass(window.utils.getPrefixClass(tag));
				}

				$tag.text(tag);
			$lineTwo.append($tag);

			var $subject = $('');
				$subject.text(" in " + window.hwView.subjects[ev.subject].name);
			$lineTwo.append($subject);

			var $due = $('');
				var keyword = "due";
				if (tag.toLowerCase() == "test" || tag.toLowerCase() == "exam" || tag.toLowerCase() == "midterm" || tag.toLowerCase() == "quiz" || tag.toLowerCase() == "ica" || tag.toLowerCase() == "lab") {
					keyword = "on";
				}
				var dueText = window.utils.formatDate_pretty(moment(ev.due).add(1, "day").toDate());
				if (moment(ev.due).add(1, "day").week() == moment().week()) {
					dueText = window.utils.getDayOfWeek(moment(ev.due).add(1, "day").day());
				}
				$due.text(" " + keyword + " " + dueText);
			$lineTwo.append($due);
		// append comes later

		if (window.hwView.swapOrder) {
			$item.append($lineTwo);
			$item.append($name);
		} else {
			$item.append($name);
			$item.append($lineTwo);
		}

	$(".hwView-" + list + " ul").append($item);
};

window.hwView.loadList = function(date, list, callback) {
	for (var subjectIndex in window.hwView.subjects) {
		window.api.get("planner/events/get/" + window.utils.formatDate_api(date) + "/" + subjectIndex, function(data) {
			var ev = data.events;
			window.hwView.loadStep();
			if (ev.length == 0) {
				return;
			}
			for (var evIndex in ev) {
				var evObj = {
					name: ev[evIndex].text,
					due: new Date(data.date),
					subject: ev[evIndex].sectionIndex,
					done: ev[evIndex].done
				};
				window.hwView.addEventToList(evObj, list);
			};
		});
	}
};

window.hwView.loadSubjects = function(callback) {
	window.hwView.subjects = {};
	window.api.get("planner/sections/get/", function(data) {
		for (var i = 0; i < data.sections.length; i++) {
			var itm = data.sections[i];
			window.hwView.subjects[itm.sectionIndex] = itm;
		};
		callback(data.sections);
	});
};

window.hwView.findNextDay = function(offset) {
	var retVal = moment(); //moment().add("days", offset);
	if (retVal.day() == 6) {
		// don't start on Saturday
		retVal.add(1, "day");
	}
	for (var i = 0; i < offset; i++) {
		retVal.add(1, "day"); //add a day
		// is it a Saturday?
		if (retVal.day() == 6) {
			retVal.add(2, "day"); // skip the weekend
		}
	}
	return retVal.toDate();
};

window.hwView.loadStep = function() {
	window.hwView.loadCount += 1;
	if (window.hwView.loadCount >= 2) { //(1 + (14 * Object.keys(window.hwView.subjects).length))) {
		window.page.hideLoading();
		if ($(".hwView-tomorrow ul").text() == "" && $(".hwView-soon ul").text() == "" && $(".hwView-longterm ul").text() == "") {
			// empty!
			swal("You don't have any homework in your planner!", " Once you've written some assignments in, come back here.", "warning")
		}
	}
};

window.hwView.loadEvents = function(callback) {
	window.hwView.loadSubjects(function(data) {
		window.hwView.loadStep();
		window.api.get("hwView/getHw?date=" + moment().format('YYYY-MM-DD'), function(data) {
			var ev = data.events;
			if (ev.length == 0) {
				window.hwView.loadStep();
				return;
			}
			for (var evIndex in ev) {
				var evObj = {
					name: ev[evIndex].text,
					due: new Date(ev[evIndex].date.split("T")[0]),
					subject: ev[evIndex].sectionIndex,
					done: ev[evIndex].done
				};
				var list = "longterm";
				var dueMoment = moment(evObj.due).utcOffset(0);
				var tomorrow = moment(window.hwView.findNextDay(1)).date();
				if (dueMoment.date() == tomorrow && dueMoment.month() == moment(window.hwView.findNextDay(1)).month() && dueMoment.year() == moment(window.hwView.findNextDay(1)).year()) { // moment.isSame didn't work here. /shrug
					list = "tomorrow";
				} else if (dueMoment.isBefore(moment(window.hwView.findNextDay(5)).subtract(1, "day"))) {
					list = "soon";
				}
				window.hwView.addEventToList(evObj, list);
			};
			window.hwView.loadStep();
		});
		/*window.hwView.loadStep();
		window.hwView.loadList(window.hwView.findNextDay(1), "tomorrow", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(2), "soon", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(3), "soon", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(4), "soon", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(5), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(6), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(7), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(8), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(9), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(10), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(11), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(12), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(13), "longterm", function() {

		});
		window.hwView.loadList(window.hwView.findNextDay(14), "longterm", function() {

		});*/
	});
	callback();
};

$(document).ready(function() {
	$("#hwView").on("tabOpened", function() {
		window.page.showLoading();
		window.hwView.loadCount = 0;
		// clear lists
		$(".hwView-tomorrow ul").text("");
		$(".hwView-soon ul").text("");
		$(".hwView-longterm ul").text("");
		// special case for checking on Friday, Saturday, or Sunday
		if (moment().day() == 0 || moment().day() == 5 || moment().day() == 6) {
			$(".tomorrow-or-monday").text("Monday"); // the part that makes it show Monday stuff is in findNextDay().
		} else {
			$(".tomorrow-or-monday").text("tomorrow");
		}
		window.prefs.get("name-subj", function(val) {
			window.hwView.swapOrder = (val == "1");
			window.prefs.get("titleclr", function(val) {
				window.hwView.clr = (val == "1");
				window.hwView.loadEvents(function() {
				});
			});

		});
	});
});
<|endoftext|>


<commit_before>/*jslint newcap: true */
/*global XMLHttpRequest: false, FormData: false */
/*
 * Inline Text Attachment
 *
 * Author: Roy van Kaathoven
 * Contact: ik@royvankaathoven.nl
 */
(function(document, window) {
  'use strict';

  var inlineAttachment = function(options, instance) {
    this.settings = inlineAttachment.util.merge(options, inlineAttachment.defaults);
    this.editor = instance;
    this.filenameTag = '{filename}';
    this.lastValue = null;
  };

  /**
   * Will holds the available editors
   *
   * @type {Object}
   */
  inlineAttachment.editors = {};

  /**
   * Utility functions
   */
  inlineAttachment.util = {

    /**
     * Simple function to merge the given objects
     *
     * @param {Object[]} object Multiple object parameters
     * @returns {Object}
     */
    merge: function() {
      var result = {};
      for (var i = arguments.length - 1; i >= 0; i--) {
        var obj = arguments[i];
        for (var k in obj) {
          if (obj.hasOwnProperty(k)) {
            result[k] = obj[k];
          }
        }
      }
      return result;
    },

    /**
     * Append a line of text at the bottom, ensuring there aren't unnecessary newlines
     *
     * @param {String} appended Current content
     * @param {String} previous Value which should be appended after the current content
     */
    appendInItsOwnLine: function(previous, appended) {
      return (previous + "\n\n[[D]]" + appended)
        .replace(/(\n{2,})\[\[D\]\]/, "\n\n")
        .replace(/^(\n*)/, "");
    },

    /**
     * Inserts the given value at the current cursor position of the textarea element
     *
     * @param  {HtmlElement} el
     * @param  {String} value Text which will be inserted at the cursor position
     */
    insertTextAtCursor: function(el, text) {
      var scrollPos = el.scrollTop,
        strPos = 0,
        browser = false,
        range;

      if ((el.selectionStart || el.selectionStart === '0')) {
        browser = "ff";
      } else if (document.selection) {
        browser = "ie";
      }

      if (browser === "ie") {
        el.focus();
        range = document.selection.createRange();
        range.moveStart('character', -el.value.length);
        strPos = range.text.length;
      } else if (browser === "ff") {
        strPos = el.selectionStart;
      }

      var front = (el.value).substring(0, strPos);
      var back = (el.value).substring(strPos, el.value.length);
      el.value = front + text + back;
      strPos = strPos + text.length;
      if (browser === "ie") {
        el.focus();
        range = document.selection.createRange();
        range.moveStart('character', -el.value.length);
        range.moveStart('character', strPos);
        range.moveEnd('character', 0);
        range.select();
      } else if (browser === "ff") {
        el.selectionStart = strPos;
        el.selectionEnd = strPos;
        el.focus();
      }
      el.scrollTop = scrollPos;
    }
  };

  /**
   * Default configuration options
   *
   * @type {Object}
   */
  inlineAttachment.defaults = {
    /**
     * URL where the file will be send
     */
    uploadUrl: 'uploadimage',

    /**
     * Which method will be used to send the file to the upload URL
     */
    uploadMethod: 'POST',

    /**
     * Name in which the file will be placed
     */
    uploadFieldName: 'image',

    /**
     * Extension which will be used when a file extension could not
     * be detected
     */
    defaultExtension: 'png',

    /**
     * JSON field which refers to the uploaded file URL
     */
    jsonFieldName: 'link',

    /**
     * Allowed MIME types
     */
    allowedTypes: window.allowedUploadMimeTypes,

    /**
     * Text which will be inserted when dropping or pasting a file.
     * Acts as a placeholder which will be replaced when the file is done with uploading
     */
    progressText: '![Uploading file...{filename}]()',

    /**
     * When a file has successfully been uploaded the progressText
     * will be replaced by the urlText, the {filename} tag will be replaced
     * by the filename that has been returned by the server
     */
    urlText: "![]({filename})",

    /**
     * Text which will be used when uploading has failed
     */
    errorText: "Error uploading file",

    /**
     * Extra parameters which will be send when uploading a file
     */
    extraParams: {},

    /**
     * Extra headers which will be send when uploading a file
     */
    extraHeaders: {},

    /**
     * Before the file is send
     */
    beforeFileUpload: function() {
      return true;
    },

    /**
     * Triggers when a file is dropped or pasted
     */
    onFileReceived: function() {},

    /**
     * Custom upload handler
     *
     * @return {Boolean} when false is returned it will prevent default upload behavior
     */
    onFileUploadResponse: function() {
      return true;
    },

    /**
     * Custom error handler. Runs after removing the placeholder text and before the alert().
     * Return false from this function to prevent the alert dialog.
     *
     * @return {Boolean} when false is returned it will prevent default error behavior
     */
    onFileUploadError: function() {
      return true;
    },

    /**
     * When a file has succesfully been uploaded
     */
    onFileUploaded: function() {}
  };

  /**
   * Uploads the blob
   *
   * @param  {Blob} file blob data received from event.dataTransfer object
   * @return {XMLHttpRequest} request object which sends the file
   */
  inlineAttachment.prototype.uploadFile = function(file, id) {
    var me = this,
      formData = new FormData(),
      xhr = new XMLHttpRequest(),
      id = id,
      settings = this.settings,
      extension = settings.defaultExtension;

    if (typeof settings.setupFormData === 'function') {
      settings.setupFormData(formData, file);
    }

    // Attach the file. If coming from clipboard, add a default filename (only works in Chrome for now)
    // http://stackoverflow.com/questions/6664967/how-to-give-a-blob-uploaded-as-formdata-a-file-name
    if (file.name) {
      var fileNameMatches = file.name.match(/\.(.+)$/);
      if (fileNameMatches) {
        extension = fileNameMatches[1];
      }
    }

    var remoteFilename = "image-" + Date.now() + "." + extension;
    if (typeof settings.remoteFilename === 'function') {
      remoteFilename = settings.remoteFilename(file);
    }

    formData.append(settings.uploadFieldName, file, remoteFilename);

    // Append the extra parameters to the formdata
    if (typeof settings.extraParams === "object") {
      for (var key in settings.extraParams) {
        if (settings.extraParams.hasOwnProperty(key)) {
          formData.append(key, settings.extraParams[key]);
        }
      }
    }

    xhr.open('POST', settings.uploadUrl);

    // Add any available extra headers
    if (typeof settings.extraHeaders === "object") {
        for (var header in settings.extraHeaders) {
            if (settings.extraHeaders.hasOwnProperty(header)) {
                xhr.setRequestHeader(header, settings.extraHeaders[header]);
            }
        }
    }

    xhr.onload = function() {
      // If HTTP status is OK or Created
      if (xhr.status === 200 || xhr.status === 201) {
        me.onFileUploadResponse(xhr, id);
      } else {
        me.onFileUploadError(xhr, id);
      }
    };
    if (settings.beforeFileUpload(xhr) !== false) {
      xhr.send(formData);
    }
    return xhr;
  };

  /**
   * Returns if the given file is allowed to handle
   *
   * @param {File} clipboard data file
   */
  inlineAttachment.prototype.isFileAllowed = function(file) {
    if (this.settings.allowedTypes.indexOf('*') === 0){
      return true;
    } else {
      return this.settings.allowedTypes.indexOf(file.type) >= 0;
    }
  };

  /**
   * Handles upload response
   *
   * @param  {XMLHttpRequest} xhr
   * @return {Void}
   */
  inlineAttachment.prototype.onFileUploadResponse = function(xhr, id) {
    if (this.settings.onFileUploadResponse.call(this, xhr) !== false) {
      var result = JSON.parse(xhr.responseText),
        filename = result[this.settings.jsonFieldName];

      if (result && filename) {
          var replacements = [];
          var string = this.settings.progressText.replace(this.filenameTag, id);
          var lines = this.editor.getValue().split('\n');
        var newValue = this.settings.urlText.replace(this.filenameTag, filename);
          for(var i = 0; i < lines.length; i++) {
            var ch = lines[i].indexOf(string);
            if(ch != -1)
                replacements.push({replacement:newValue, from:{line:i, ch:ch}, to:{line:i, ch:ch + string.length}});
        }
          for(var i = 0; i < replacements.length; i++)
            this.editor.replaceRange(replacements[i]);
      }
    }
  };

  /**
   * Called when a file has failed to upload
   *
   * @param  {XMLHttpRequest} xhr
   * @return {Void}
   */
  inlineAttachment.prototype.onFileUploadError = function(xhr, id) {
    if (this.settings.onFileUploadError.call(this, xhr) !== false) {
      var replacements = [];
      var string = this.settings.progressText.replace(this.filenameTag, id);
      var lines = this.editor.getValue().split('\n');
        for(var i = 0; i < lines.length; i++) {
            var ch = lines[i].indexOf(this.lastValue);
            if(ch != -1)
                replacements.push({replacement:"", from:{line:i, ch:ch}, to:{line:i, ch:ch + string.length}});
        }
        for(var i = 0; i < replacements.length; i++)
            this.editor.replaceRange(replacements[i]);
    }
  };

  /**
   * Called when a file has been inserted, either by drop or paste
   *
   * @param  {File} file
   * @return {Void}
   */
  inlineAttachment.prototype.onFileInserted = function(file, id) {
    if (this.settings.onFileReceived.call(this, file) !== false) {
      this.lastValue = this.settings.progressText.replace(this.filenameTag, id);
      this.editor.insertValue(this.lastValue + "\n");
    }
  };

  /**
   * Called when a paste event occured
   * @param  {Event} e
   * @return {Boolean} if the event was handled
   */
  inlineAttachment.prototype.onPaste = function(e) {
    var result = false,
      clipboardData = e.clipboardData,
      items;

    if (typeof clipboardData === "object") {
      items = clipboardData.items || clipboardData.files || [];

      for (var i = 0; i < items.length; i++) {
        var item = items[i];
        if (this.isFileAllowed(item)) {
          result = true;
          var id = ID();
          this.onFileInserted(item.getAsFile(), id);
          this.uploadFile(item.getAsFile(), id);
        }
      }
    }

    if (result) { e.preventDefault(); }

    return result;
  };

  /**
   * Called when a drop event occures
   * @param  {Event} e
   * @return {Boolean} if the event was handled
   */
  inlineAttachment.prototype.onDrop = function(e) {
    var result = false;
    for (var i = 0; i < e.dataTransfer.files.length; i++) {
      var file = e.dataTransfer.files[i];
      if (this.isFileAllowed(file)) {
        result = true;
        var id = ID();
        this.onFileInserted(file, id);
        this.uploadFile(file, id);
      }
    }

    return result;
  };

  window.inlineAttachment = inlineAttachment;

})(document, window);

// Generate unique IDs for use as pseudo-private/protected names.
// Similar in concept to
// <http://wiki.ecmascript.org/doku.php?id=strawman:names>.
//
// The goals of this function are twofold:
//
// * Provide a way to generate a string guaranteed to be unique when compared
//   to other strings generated by this function.
// * Make the string complex enough that it is highly unlikely to be
//   accidentally duplicated by hand (this is key if you're using `ID`
//   as a private/protected name on an object).
//
// Use:
//
//     var privateName = ID();
//     var o = { 'public': 'foo' };
//     o[privateName] = 'bar';
var ID = function () {
  // Math.random should be unique because of its seeding algorithm.
  // Convert it to base 36 (numbers + letters), and grab the first 9 characters
  // after the decimal.
  return '_' + Math.random().toString(36).substr(2, 9);
};
<commit_msg>Reorganize usage of `getAsFile()`<commit_after>/*jslint newcap: true */
/*global XMLHttpRequest: false, FormData: false */
/*
 * Inline Text Attachment
 *
 * Author: Roy van Kaathoven
 * Contact: ik@royvankaathoven.nl
 */
(function(document, window) {
  'use strict';

  var inlineAttachment = function(options, instance) {
    this.settings = inlineAttachment.util.merge(options, inlineAttachment.defaults);
    this.editor = instance;
    this.filenameTag = '{filename}';
    this.lastValue = null;
  };

  /**
   * Will holds the available editors
   *
   * @type {Object}
   */
  inlineAttachment.editors = {};

  /**
   * Utility functions
   */
  inlineAttachment.util = {

    /**
     * Simple function to merge the given objects
     *
     * @param {Object[]} object Multiple object parameters
     * @returns {Object}
     */
    merge: function() {
      var result = {};
      for (var i = arguments.length - 1; i >= 0; i--) {
        var obj = arguments[i];
        for (var k in obj) {
          if (obj.hasOwnProperty(k)) {
            result[k] = obj[k];
          }
        }
      }
      return result;
    },

    /**
     * Append a line of text at the bottom, ensuring there aren't unnecessary newlines
     *
     * @param {String} appended Current content
     * @param {String} previous Value which should be appended after the current content
     */
    appendInItsOwnLine: function(previous, appended) {
      return (previous + "\n\n[[D]]" + appended)
        .replace(/(\n{2,})\[\[D\]\]/, "\n\n")
        .replace(/^(\n*)/, "");
    },

    /**
     * Inserts the given value at the current cursor position of the textarea element
     *
     * @param  {HtmlElement} el
     * @param  {String} value Text which will be inserted at the cursor position
     */
    insertTextAtCursor: function(el, text) {
      var scrollPos = el.scrollTop,
        strPos = 0,
        browser = false,
        range;

      if ((el.selectionStart || el.selectionStart === '0')) {
        browser = "ff";
      } else if (document.selection) {
        browser = "ie";
      }

      if (browser === "ie") {
        el.focus();
        range = document.selection.createRange();
        range.moveStart('character', -el.value.length);
        strPos = range.text.length;
      } else if (browser === "ff") {
        strPos = el.selectionStart;
      }

      var front = (el.value).substring(0, strPos);
      var back = (el.value).substring(strPos, el.value.length);
      el.value = front + text + back;
      strPos = strPos + text.length;
      if (browser === "ie") {
        el.focus();
        range = document.selection.createRange();
        range.moveStart('character', -el.value.length);
        range.moveStart('character', strPos);
        range.moveEnd('character', 0);
        range.select();
      } else if (browser === "ff") {
        el.selectionStart = strPos;
        el.selectionEnd = strPos;
        el.focus();
      }
      el.scrollTop = scrollPos;
    }
  };

  /**
   * Default configuration options
   *
   * @type {Object}
   */
  inlineAttachment.defaults = {
    /**
     * URL where the file will be send
     */
    uploadUrl: 'uploadimage',

    /**
     * Which method will be used to send the file to the upload URL
     */
    uploadMethod: 'POST',

    /**
     * Name in which the file will be placed
     */
    uploadFieldName: 'image',

    /**
     * Extension which will be used when a file extension could not
     * be detected
     */
    defaultExtension: 'png',

    /**
     * JSON field which refers to the uploaded file URL
     */
    jsonFieldName: 'link',

    /**
     * Allowed MIME types
     */
    allowedTypes: window.allowedUploadMimeTypes,

    /**
     * Text which will be inserted when dropping or pasting a file.
     * Acts as a placeholder which will be replaced when the file is done with uploading
     */
    progressText: '![Uploading file...{filename}]()',

    /**
     * When a file has successfully been uploaded the progressText
     * will be replaced by the urlText, the {filename} tag will be replaced
     * by the filename that has been returned by the server
     */
    urlText: "![]({filename})",

    /**
     * Text which will be used when uploading has failed
     */
    errorText: "Error uploading file",

    /**
     * Extra parameters which will be send when uploading a file
     */
    extraParams: {},

    /**
     * Extra headers which will be send when uploading a file
     */
    extraHeaders: {},

    /**
     * Before the file is send
     */
    beforeFileUpload: function() {
      return true;
    },

    /**
     * Triggers when a file is dropped or pasted
     */
    onFileReceived: function() {},

    /**
     * Custom upload handler
     *
     * @return {Boolean} when false is returned it will prevent default upload behavior
     */
    onFileUploadResponse: function() {
      return true;
    },

    /**
     * Custom error handler. Runs after removing the placeholder text and before the alert().
     * Return false from this function to prevent the alert dialog.
     *
     * @return {Boolean} when false is returned it will prevent default error behavior
     */
    onFileUploadError: function() {
      return true;
    },

    /**
     * When a file has succesfully been uploaded
     */
    onFileUploaded: function() {}
  };

  /**
   * Uploads the blob
   *
   * @param  {Blob} file blob data received from event.dataTransfer object
   * @return {XMLHttpRequest} request object which sends the file
   */
  inlineAttachment.prototype.uploadFile = function(file, id) {
    var me = this,
      formData = new FormData(),
      xhr = new XMLHttpRequest(),
      id = id,
      settings = this.settings,
      extension = settings.defaultExtension;

    if (typeof settings.setupFormData === 'function') {
      settings.setupFormData(formData, file);
    }

    // Attach the file. If coming from clipboard, add a default filename (only works in Chrome for now)
    // http://stackoverflow.com/questions/6664967/how-to-give-a-blob-uploaded-as-formdata-a-file-name
    if (file.name) {
      var fileNameMatches = file.name.match(/\.(.+)$/);
      if (fileNameMatches) {
        extension = fileNameMatches[1];
      }
    }

    var remoteFilename = "image-" + Date.now() + "." + extension;
    if (typeof settings.remoteFilename === 'function') {
      remoteFilename = settings.remoteFilename(file);
    }

    formData.append(settings.uploadFieldName, file, remoteFilename);

    // Append the extra parameters to the formdata
    if (typeof settings.extraParams === "object") {
      for (var key in settings.extraParams) {
        if (settings.extraParams.hasOwnProperty(key)) {
          formData.append(key, settings.extraParams[key]);
        }
      }
    }

    xhr.open('POST', settings.uploadUrl);

    // Add any available extra headers
    if (typeof settings.extraHeaders === "object") {
        for (var header in settings.extraHeaders) {
            if (settings.extraHeaders.hasOwnProperty(header)) {
                xhr.setRequestHeader(header, settings.extraHeaders[header]);
            }
        }
    }

    xhr.onload = function() {
      // If HTTP status is OK or Created
      if (xhr.status === 200 || xhr.status === 201) {
        me.onFileUploadResponse(xhr, id);
      } else {
        me.onFileUploadError(xhr, id);
      }
    };
    if (settings.beforeFileUpload(xhr) !== false) {
      xhr.send(formData);
    }
    return xhr;
  };

  /**
   * Returns if the given file is allowed to handle
   *
   * @param {File} clipboard data file
   */
  inlineAttachment.prototype.isFileAllowed = function(file) {
    if (this.settings.allowedTypes.indexOf('*') === 0){
      return true;
    } else {
      return this.settings.allowedTypes.indexOf(file.type) >= 0;
    }
  };

  /**
   * Handles upload response
   *
   * @param  {XMLHttpRequest} xhr
   * @return {Void}
   */
  inlineAttachment.prototype.onFileUploadResponse = function(xhr, id) {
    if (this.settings.onFileUploadResponse.call(this, xhr) !== false) {
      var result = JSON.parse(xhr.responseText),
        filename = result[this.settings.jsonFieldName];

      if (result && filename) {
          var replacements = [];
          var string = this.settings.progressText.replace(this.filenameTag, id);
          var lines = this.editor.getValue().split('\n');
        var newValue = this.settings.urlText.replace(this.filenameTag, filename);
          for(var i = 0; i < lines.length; i++) {
            var ch = lines[i].indexOf(string);
            if(ch != -1)
                replacements.push({replacement:newValue, from:{line:i, ch:ch}, to:{line:i, ch:ch + string.length}});
        }
          for(var i = 0; i < replacements.length; i++)
            this.editor.replaceRange(replacements[i]);
      }
    }
  };

  /**
   * Called when a file has failed to upload
   *
   * @param  {XMLHttpRequest} xhr
   * @return {Void}
   */
  inlineAttachment.prototype.onFileUploadError = function(xhr, id) {
    if (this.settings.onFileUploadError.call(this, xhr) !== false) {
      var replacements = [];
      var string = this.settings.progressText.replace(this.filenameTag, id);
      var lines = this.editor.getValue().split('\n');
        for(var i = 0; i < lines.length; i++) {
            var ch = lines[i].indexOf(this.lastValue);
            if(ch != -1)
                replacements.push({replacement:"", from:{line:i, ch:ch}, to:{line:i, ch:ch + string.length}});
        }
        for(var i = 0; i < replacements.length; i++)
            this.editor.replaceRange(replacements[i]);
    }
  };

  /**
   * Called when a file has been inserted, either by drop or paste
   *
   * @param  {File} file
   * @return {Void}
   */
  inlineAttachment.prototype.onFileInserted = function(file, id) {
    if (this.settings.onFileReceived.call(this, file) !== false) {
      this.lastValue = this.settings.progressText.replace(this.filenameTag, id);
      this.editor.insertValue(this.lastValue + "\n");
    }
  };

  /**
   * Called when a paste event occured
   * @param  {Event} e
   * @return {Boolean} if the event was handled
   */
  inlineAttachment.prototype.onPaste = function(e) {
    var result = false,
      clipboardData = e.clipboardData,
      items;

    if (typeof clipboardData === "object") {
      items = clipboardData.items || clipboardData.files || [];

      for (var i = 0; i < items.length; i++) {
        var item = items[i];
        if (this.isFileAllowed(item)) {
          result = true;
          var id = ID();
          var file = item.getAsFile();
          if (file !== null) {
            this.onFileInserted(file, id);
            this.uploadFile(file, id);
          }
        }
      }
    }

    if (result) { e.preventDefault(); }

    return result;
  };

  /**
   * Called when a drop event occures
   * @param  {Event} e
   * @return {Boolean} if the event was handled
   */
  inlineAttachment.prototype.onDrop = function(e) {
    var result = false;
    for (var i = 0; i < e.dataTransfer.files.length; i++) {
      var file = e.dataTransfer.files[i];
      if (this.isFileAllowed(file)) {
        result = true;
        var id = ID();
        this.onFileInserted(file, id);
        this.uploadFile(file, id);
      }
    }

    return result;
  };

  window.inlineAttachment = inlineAttachment;

})(document, window);

// Generate unique IDs for use as pseudo-private/protected names.
// Similar in concept to
// <http://wiki.ecmascript.org/doku.php?id=strawman:names>.
//
// The goals of this function are twofold:
//
// * Provide a way to generate a string guaranteed to be unique when compared
//   to other strings generated by this function.
// * Make the string complex enough that it is highly unlikely to be
//   accidentally duplicated by hand (this is key if you're using `ID`
//   as a private/protected name on an object).
//
// Use:
//
//     var privateName = ID();
//     var o = { 'public': 'foo' };
//     o[privateName] = 'bar';
var ID = function () {
  // Math.random should be unique because of its seeding algorithm.
  // Convert it to base 36 (numbers + letters), and grab the first 9 characters
  // after the decimal.
  return '_' + Math.random().toString(36).substr(2, 9);
};
<|endoftext|>


<commit_before>const moduleError = /Error: Cannot find module '([a-zA-Z0-9+_-]+)'/g;

const gulp = require('gulp');
const eslint = require('gulp-eslint');
const spawn = require('child_process').spawn;

const isDev = process.argv[2] === '--dev';

let bot;

const paths = {
    srcFiles: 'src/**/!(_)*.js',
    configFiles: 'data/configs/config.json',
    gulpFile: 'gulpfile.js'
};

function killBot() {
    if (bot) bot.kill();
}

gulp.task('kill', () => {
    killBot();
});

gulp.task('lint', () =>
    gulp.src([
        paths.srcFiles,
        paths.gulpFile
    ])
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failAfterError())
);

const mainTasks = ['kill'];
if (isDev) {
    mainTasks.push('lint');
}

gulp.task('main', mainTasks, () => {
    bot = spawn('node', ['src/bot.js'], { 'stdio': ['inherit', 'inherit', 'pipe'] });

    bot.stderr.on('data', data => {
        process.stderr.write(data);
        if (moduleError.test(data.toString())) {
            console.error(`
#########################################################################################################################
 Node has failed to load a module! If you just updated, you may need to run \'yarn\' again to install/update dependencies.
 'yarn' will attempt to run now and install the dependencies for you.
#########################################################################################################################
`);
            spawn('yarn', ['--force'], { 'stdio': 'inherit' }).on('close', code => {
                if (code === 0) {
                    console.log(`
New modules have been installed. The bot will now restart.
                `);
                    gulp.start('main');
                }
            });
        }
    });

    bot.on('close', code => {
        if (code === null) {
            // Killed by killBot()
            return;
        }

        if (code === 42) {
            // Restart
            console.error('Restart code detected.');
            gulp.start('main');
        } else if (code === 666) {
            // Full process stop
            console.log('Process exit code detected.');
            process.exit(1);
        } else {
            // Wait for changes
            console.log('Bot has exited. Waiting for changes...');
        }
    });
});

gulp.task('watch', () => {
    gulp.watch([
        paths.srcFiles,
        paths.configFiles
    ], ['main']);
});

gulp.task('default', ['main', 'watch']);

process.on('exit', () => {
    killBot();
});
<commit_msg>Added check for Node v6<commit_after>if (process.version.startsWith('v6')) {
    console.error('[ERROR] SharpBot requires Node v7 or greater. Please download it at https://nodejs.org/en/download/current.');
    console.error('- Windows - https://nodejs.org/en/download/current');
    console.error('- macOS - https://nodejs.org/en/download/current');
    console.error('- Linux - https://nodejs.org/en/download/package-manager');
    process.exit(1);
}

const moduleError = /Error: Cannot find module '([a-zA-Z0-9+_-]+)'/g;

const gulp = require('gulp');
const eslint = require('gulp-eslint');
const spawn = require('child_process').spawn;

const isDev = process.argv[2] === '--dev';

let bot;

const paths = {
    srcFiles: 'src/**/!(_)*.js',
    configFiles: 'data/configs/config.json',
    gulpFile: 'gulpfile.js'
};

function killBot() {
    if (bot) bot.kill();
}

gulp.task('kill', () => {
    killBot();
});

gulp.task('lint', () =>
    gulp.src([
        paths.srcFiles,
        paths.gulpFile
    ])
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failAfterError())
);

const mainTasks = ['kill'];
if (isDev) {
    mainTasks.push('lint');
}

gulp.task('main', mainTasks, () => {
    bot = spawn('node', ['src/bot.js'], { 'stdio': ['inherit', 'inherit', 'pipe'] });

    bot.stderr.on('data', data => {
        process.stderr.write(data);
        if (moduleError.test(data.toString())) {
            console.error(`
#########################################################################################################################
 Node has failed to load a module! If you just updated, you may need to run \'yarn\' again to install/update dependencies.
 'yarn' will attempt to run now and install the dependencies for you.
#########################################################################################################################
`);
            spawn('yarn', ['--force'], { 'stdio': 'inherit' }).on('close', code => {
                if (code === 0) {
                    console.log(`
New modules have been installed. The bot will now restart.
                `);
                    gulp.start('main');
                }
            });
        }
    });

    bot.on('close', code => {
        if (code === null) {
            // Killed by killBot()
            return;
        }

        if (code === 42) {
            // Restart
            console.error('Restart code detected.');
            gulp.start('main');
        } else if (code === 666) {
            // Full process stop
            console.log('Process exit code detected.');
            process.exit(1);
        } else {
            // Wait for changes
            console.log('Bot has exited. Waiting for changes...');
        }
    });
});

gulp.task('watch', () => {
    gulp.watch([
        paths.srcFiles,
        paths.configFiles
    ], ['main']);
});

gulp.task('default', ['main', 'watch']);

process.on('exit', () => {
    killBot();
});
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies
 */
var _ = require('lodash'),
	defaultAssets = require('./config/assets/default'),
	testAssets = require('./config/assets/test'),
	gulp = require('gulp'),
	gulpLoadPlugins = require('gulp-load-plugins'),
	runSequence = require('run-sequence'),
	plugins = gulpLoadPlugins();

// Set NODE_ENV to 'test'
gulp.task('env:test', function () {
	process.env.NODE_ENV = 'test';
});

// Set NODE_ENV to 'development'
gulp.task('env:dev', function () {
	process.env.NODE_ENV = 'development';
});

// Set NODE_ENV to 'production'
gulp.task('env:prod', function () {
	process.env.NODE_ENV = 'production';
});

// Set DEBUG environment variables
gulp.task('env:DEBUG', function () {
    console.log('debug');
    process.env.DEBUG = 'express:*';
})

// Nodemon task
gulp.task('nodemon', function () {
	return plugins.nodemon({
		script: 'server.js',
		nodeArgs: ['--debug'],
		ext: 'js,html',
		watch: _.union(defaultAssets.server.views, defaultAssets.server.allJS, defaultAssets.server.config)
	});
});

// Watch Files For Changes
gulp.task('watch', function() {
	// Start livereload
	plugins.livereload.listen();

	// Add watch rules
	gulp.watch(defaultAssets.server.views).on('change', plugins.livereload.changed);
	gulp.watch(defaultAssets.server.allJS, ['jshint']).on('change', plugins.livereload.changed);
	gulp.watch(defaultAssets.client.views).on('change', plugins.livereload.changed);
	gulp.watch(defaultAssets.client.js, ['jshint']).on('change', plugins.livereload.changed);
	gulp.watch(defaultAssets.client.css, ['csslint']).on('change', plugins.livereload.changed);
	gulp.watch(defaultAssets.client.sass, ['sass', 'csslint']).on('change', plugins.livereload.changed);
	gulp.watch(defaultAssets.client.less, ['less', 'csslint']).on('change', plugins.livereload.changed);
});

// CSS linting task
gulp.task('csslint', function (done) {
	return gulp.src(defaultAssets.client.css)
		.pipe(plugins.csslint('.csslintrc'))
		.pipe(plugins.csslint.reporter())
		.pipe(plugins.csslint.reporter(function (file) {
			if (!file.csslint.errorCount) {
				done();
			}
		}));
});

// JS linting task
gulp.task('jshint', function () {
	return gulp.src(_.union(defaultAssets.server.allJS, defaultAssets.client.js, testAssets.tests.server, testAssets.tests.client, testAssets.tests.e2e))
		.pipe(plugins.jshint())
		.pipe(plugins.jshint.reporter('default'))
		.pipe(plugins.jshint.reporter('fail'));
});

// JS minifying task
gulp.task('uglify', function () {
	return gulp.src(defaultAssets.client.js)
		.pipe(plugins.ngAnnotate())
		.pipe(plugins.uglify({
			mangle: false
		}))
		.pipe(plugins.concat('application.min.js'))
		.pipe(gulp.dest('public/dist'));
});

// CSS minifying task
gulp.task('cssmin', function () {
	return gulp.src(defaultAssets.client.css)
		.pipe(plugins.cssmin())
		.pipe(plugins.concat('application.min.css'))
		.pipe(gulp.dest('public/dist'));
});

// Sass task
gulp.task('sass', function () {
	return gulp.src(defaultAssets.client.sass)
		.pipe(plugins.sass())
		.pipe(plugins.rename(function (path) {
			path.dirname = path.dirname.replace('/scss', '/css');
		}))
		.pipe(gulp.dest('./modules/'));
});

// Less task
gulp.task('less', function () {
	return gulp.src(defaultAssets.client.less)
		.pipe(plugins.less())
		.pipe(plugins.rename(function (path) {
			path.dirname = path.dirname.replace('/less', '/css');
		}))
		.pipe(gulp.dest('./modules/'));
});

// Mocha tests task
gulp.task('mocha', function (done) {
	// Open mongoose connections
	var mongoose = require('./config/lib/mongoose.js');
	var error;

	// Connect mongoose
	mongoose.connect(function() {
		// Run the tests
		gulp.src(testAssets.tests.server)
			.pipe(plugins.mocha({
				reporter: 'spec'
			}))
			.on('error', function (err) {
				// If an error occurs, save it
				error = err;
			})
			.on('end', function() {
				// When the tests are done, disconnect mongoose and pass the error state back to gulp
				mongoose.disconnect(function() {
					done(error);
				});
			});
	});

});

// Karma test runner task
gulp.task('karma', function (done) {
	return gulp.src([])
		.pipe(plugins.karma({
			configFile: 'karma.conf.js',
			action: 'run',
			singleRun: true
		}));
});

// Selenium standalone WebDriver update task
gulp.task('webdriver-update', plugins.protractor.webdriver_update);

// Protractor test runner task
gulp.task('protractor', function () {
	gulp.src([])
		.pipe(plugins.protractor.protractor({
			configFile: 'protractor.conf.js'
		}))
		.on('error', function (e) {
			throw e;
		});
});

// Lint CSS and JavaScript files.
gulp.task('lint', function(done) {
	runSequence('less', 'sass', ['csslint', 'jshint'], done);
});

// Lint project files and minify them into two production files.
gulp.task('build', function(done) {
	runSequence('env:dev' ,'lint', ['uglify', 'cssmin'], done);
});

// Run the project tests
gulp.task('test', function(done) {
	runSequence('env:test', ['karma', 'mocha'], done);
});

// Run the project in development mode
gulp.task('default', function(done) {
	runSequence('env:dev', 'lint', ['nodemon', 'watch'], done);
});

// Run the project in debug mode
gulp.task('debug', function(done) {
	runSequence('env:dev', 'env:DEBUG', 'lint', ['nodemon', 'watch'], done);
});

// Run the project in production mode
gulp.task('prod', function(done) {
	runSequence('build', 'lint', 'env:prod', ['nodemon', 'watch'], done);
});
<commit_msg>huge timeout for mocha tests to overcome ghost slowness<commit_after>'use strict';

/**
 * Module dependencies
 */
var _ = require('lodash'),
	defaultAssets = require('./config/assets/default'),
	testAssets = require('./config/assets/test'),
	gulp = require('gulp'),
	gulpLoadPlugins = require('gulp-load-plugins'),
	runSequence = require('run-sequence'),
	plugins = gulpLoadPlugins();

// Set NODE_ENV to 'test'
gulp.task('env:test', function () {
	process.env.NODE_ENV = 'test';
});

// Set NODE_ENV to 'development'
gulp.task('env:dev', function () {
	process.env.NODE_ENV = 'development';
});

// Set NODE_ENV to 'production'
gulp.task('env:prod', function () {
	process.env.NODE_ENV = 'production';
});

// Set DEBUG environment variables
gulp.task('env:DEBUG', function () {
    console.log('debug');
    process.env.DEBUG = 'express:*';
})

// Nodemon task
gulp.task('nodemon', function () {
	return plugins.nodemon({
		script: 'server.js',
		nodeArgs: ['--debug'],
		ext: 'js,html',
		watch: _.union(defaultAssets.server.views, defaultAssets.server.allJS, defaultAssets.server.config)
	});
});

// Watch Files For Changes
gulp.task('watch', function() {
	// Start livereload
	plugins.livereload.listen();

	// Add watch rules
	gulp.watch(defaultAssets.server.views).on('change', plugins.livereload.changed);
	gulp.watch(defaultAssets.server.allJS, ['jshint']).on('change', plugins.livereload.changed);
	gulp.watch(defaultAssets.client.views).on('change', plugins.livereload.changed);
	gulp.watch(defaultAssets.client.js, ['jshint']).on('change', plugins.livereload.changed);
	gulp.watch(defaultAssets.client.css, ['csslint']).on('change', plugins.livereload.changed);
	gulp.watch(defaultAssets.client.sass, ['sass', 'csslint']).on('change', plugins.livereload.changed);
	gulp.watch(defaultAssets.client.less, ['less', 'csslint']).on('change', plugins.livereload.changed);
});

// CSS linting task
gulp.task('csslint', function (done) {
	return gulp.src(defaultAssets.client.css)
		.pipe(plugins.csslint('.csslintrc'))
		.pipe(plugins.csslint.reporter())
		.pipe(plugins.csslint.reporter(function (file) {
			if (!file.csslint.errorCount) {
				done();
			}
		}));
});

// JS linting task
gulp.task('jshint', function () {
	return gulp.src(_.union(defaultAssets.server.allJS, defaultAssets.client.js, testAssets.tests.server, testAssets.tests.client, testAssets.tests.e2e))
		.pipe(plugins.jshint())
		.pipe(plugins.jshint.reporter('default'))
		.pipe(plugins.jshint.reporter('fail'));
});

// JS minifying task
gulp.task('uglify', function () {
	return gulp.src(defaultAssets.client.js)
		.pipe(plugins.ngAnnotate())
		.pipe(plugins.uglify({
			mangle: false
		}))
		.pipe(plugins.concat('application.min.js'))
		.pipe(gulp.dest('public/dist'));
});

// CSS minifying task
gulp.task('cssmin', function () {
	return gulp.src(defaultAssets.client.css)
		.pipe(plugins.cssmin())
		.pipe(plugins.concat('application.min.css'))
		.pipe(gulp.dest('public/dist'));
});

// Sass task
gulp.task('sass', function () {
	return gulp.src(defaultAssets.client.sass)
		.pipe(plugins.sass())
		.pipe(plugins.rename(function (path) {
			path.dirname = path.dirname.replace('/scss', '/css');
		}))
		.pipe(gulp.dest('./modules/'));
});

// Less task
gulp.task('less', function () {
	return gulp.src(defaultAssets.client.less)
		.pipe(plugins.less())
		.pipe(plugins.rename(function (path) {
			path.dirname = path.dirname.replace('/less', '/css');
		}))
		.pipe(gulp.dest('./modules/'));
});

// Mocha tests task
gulp.task('mocha', function (done) {
	// Open mongoose connections
	var mongoose = require('./config/lib/mongoose.js');
	var error;

	// Connect mongoose
	mongoose.connect(function() {
		// Run the tests
		gulp.src(testAssets.tests.server)
			.pipe(plugins.mocha({
				reporter: 'spec',
				timeout: 8000
			}))
			.on('error', function (err) {
				// If an error occurs, save it
				error = err;
			})
			.on('end', function() {
				// When the tests are done, disconnect mongoose and pass the error state back to gulp
				mongoose.disconnect(function() {
					done(error);
				});
			});
	});

});

// Karma test runner task
gulp.task('karma', function (done) {
	return gulp.src([])
		.pipe(plugins.karma({
			configFile: 'karma.conf.js',
			action: 'run',
			singleRun: true
		}));
});

// Selenium standalone WebDriver update task
gulp.task('webdriver-update', plugins.protractor.webdriver_update);

// Protractor test runner task
gulp.task('protractor', function () {
	gulp.src([])
		.pipe(plugins.protractor.protractor({
			configFile: 'protractor.conf.js'
		}))
		.on('error', function (e) {
			throw e;
		});
});

// Lint CSS and JavaScript files.
gulp.task('lint', function(done) {
	runSequence('less', 'sass', ['csslint', 'jshint'], done);
});

// Lint project files and minify them into two production files.
gulp.task('build', function(done) {
	runSequence('env:dev' ,'lint', ['uglify', 'cssmin'], done);
});

// Run the project tests
gulp.task('test', function(done) {
	runSequence('env:test', ['karma', 'mocha'], done);
});

// Run the project in development mode
gulp.task('default', function(done) {
	runSequence('env:dev', 'lint', ['nodemon', 'watch'], done);
});

// Run the project in debug mode
gulp.task('debug', function(done) {
	runSequence('env:dev', 'env:DEBUG', 'lint', ['nodemon', 'watch'], done);
});

// Run the project in production mode
gulp.task('prod', function(done) {
	runSequence('build', 'lint', 'env:prod', ['nodemon', 'watch'], done);
});
<|endoftext|>


<commit_before>const gulp = require('gulp')
const browserSync = require('browser-sync').create()
const sass = require('gulp-sass')
const plumber = require('gulp-plumber')
const gutil = require('gulp-util')
const fs = require('fs')
const juice = require('juice')
const path = require('path')
const information = require(path.join(__dirname, 'working/information.json'))
const html2txt = require('gulp-html2txt')
const cheerio = require('cheerio')
const mailgun = require('mailgun.js')
const imagemin = require('gulp-imagemin')
const postcss = require('gulp-postcss')
const uncss = require('postcss-uncss')

// Compile sass into CSS & auto-inject into browsers
gulp.task('sass', (done) => {
  return gulp.src('working/scss/*.scss')
    // plumber is used so that the watch task does not break when there is some wrong code
    .pipe(plumber(function (error) {
      gutil.beep()
      console.log(error)
      this.emit('end')
    }))
    .pipe(sass())
    .pipe(gulp.dest('working/css'))
    .pipe(browserSync.stream())
})

// Compile sass into CSS, remove unneeded CSS and dump it into the working folder for use in build process
// it is not neccessary to create a build CSS file just for the purpose of building the final email
gulp.task('css', () => {
  var plugins = [
    uncss({
      html: ['working/index.html']
    })
  ]
  return gulp.src('working/scss/*.scss')
    .pipe(plumber(function (error) {
      gutil.beep()
      console.log(error)
      this.emit('end')
    }))
    .pipe(sass())
    .pipe(postcss(plugins))
    .pipe(gulp.dest('working/css'))
})

/**
* replaces each instance of a replacelist key template in the string for the replacelist value
* @param {string}
* @param {object}
* @return {string}
*/
const replaceLinks = (str, replacelist = {}) => {
  // there has to be a string to pass through even if replacelist is empty
  if (str.length < 1) throw new Error('File is empty')
  // if there is no replacelist or it is empty, pass through the value
  let html = str
  if (Object.keys(replacelist).length !== 0) {
    for (const key of Object.keys(replacelist)) {
      let replacestring = new RegExp(`#{${key}}`, 'g')
      html = html.replace(replacestring, replacelist[key])
    }
    let basicReg = /#{(.*?)}/g
    let els = html.match(basicReg)
    if (els !== null) {
      // find all matches and filter out the fluff
      // TODO think about if a set would be more appropriate as it would only list the unique values
      /*
      let allElements = new Set(els.map(x => x.replace('#{','').replace('}', '')))
      console.log(`Warning: there are still ${allElements.size} elements not replaced: ${[...allElements]}'`)
      */
      let allElements = els.map(x => x.replace('#{', '').replace('}', ''))
      console.log(`Warning: there are still ${allElements.length} instances not replaced: ${allElements}`)
    }
    return html
  } else {
    console.log('Warning: no links are provided, html goes through as is.')
    return str
  }
}

/**
* creates a url parameter string from the links object given
* @param {object}
* @return {string}
*/
const createParameterString = (parameters) => {
  let availableUtmKeys = ['source', 'medium', 'name', 'term', 'content']
  let paramsArr = []

  // check if the parameters exist, otherwise don't bother
  if (parameters.utm || parameters.custom) {
    // loop through all the utm and custom keys
    for (const paramKeys of Object.keys(parameters)) {
      // loop through the inner keys of utm and custom
      for (const key of Object.keys(parameters[paramKeys])) {
        // don't add if it is empty or if it is not a string or number
        if (parameters[paramKeys][key] && (typeof parameters[paramKeys][key] === 'string' || typeof parameters[paramKeys][key] === 'number')) {
          // only add the keys that are valid utm keys, ignore the ones that are not correct
          // IGNORE OR ERROR?
          if (paramKeys === 'utm' && availableUtmKeys.indexOf(key) !== -1) {
            paramsArr.push(`utm_${key}=${parameters[paramKeys][key]}`)
          } else if (paramKeys !== 'utm') { // if the keys are not utm, you are free to add them
            paramsArr.push(`${key}=${parameters[paramKeys][key]}`)
          }
        }
      }
    }
  }
  // joining all parameters in a string
  // if there are no parameters, create an empty string
  let paramsString = paramsArr.length !== 0 ? `?${paramsArr.join('&')}` : ''
  return paramsString
}

/**
* replaces each instance of a href atrribute in the first argument for the values provided in the second argument
* @param {string}
* @param {string}
* @param {function}
* @return {string}
*/
const addParameters = (str, parameters = '', cheerio) => {
  // check if the createParameterString returns a non-empty string
  // to avoid all the work that is not needed
  if (parameters) {
    // load the cheerio instance for appending links
    const $ = cheerio.load(str, { decodeEntities: false })
    // create the parameter string that will be appended to each href

    // add the parameters to each href in the email
    $('a').each(function (i, el) {
      let origHref = $(this).attr('href')
      // if the split method returns an array that is longer than 1 element, it means that there is an existing parameter in it
      // append the parameters only if there is no existing parameters
      if (origHref.split('?').length === 1) {
        $(this).attr('href', origHref + parameters)
      }
    })
    return $.html()
  } else {
    // if there is nothing available, return as is
    return str
  }
}

/**
* replaces each instance of a href atrribute in the first argument for the values provided in the second argument
* @param {string}
* @param {string}
* @return {string}
*/
const createHtml = (html, cssPath, addParameters, createParameterString, cheerio) => {
  return new Promise((resolve, reject) => {
    // replace the file path in the html file so that juice can find it, looking from the root of the project
    // replace the path for images since juice needs all files to be present
    let email = html.replace('<link rel="stylesheet" href="css/styles.css">', `<link rel="stylesheet" href="${cssPath}">`).replace('img/', 'working/img/')
    juice.juiceResources(email, { preserveMediaQueries: true, applyStyleTags: true }, (err, html) => {
      if (err) reject(err)
      // replace the url placeholder and add url parameters
      resolve(addParameters(replaceLinks(html, information.links), createParameterString(information.parameters), cheerio))
    })
  })
}

gulp.task('premailer', (done) => {
  fs.readFile('working/index.html', 'utf-8', (err, html) => {
    if (err) throw err
    createHtml(html, 'working/css/styles.css', addParameters, createParameterString, cheerio).then(html => {
      fs.writeFile('build/index.html', html.replace('working/img/', 'img/'), 'utf8', err => {
        if (err) {
          if (err.code === 'ENOENT') {
            fs.mkdir('build', err => {
              if (err) console.log(err)
              fs.writeFile('build/index.html', html.replace('working/img/', 'img/'), 'utf8', err => {
                if (err) console.log(err)
                done()
              })
            })
          }
        } else {
          done()
        }
      })
    }).catch(e => {
      console.log(e)
    })
  })
})

// convert html to txt
gulp.task('txt', () => {
  return gulp.src('build/index.html')
    .pipe(html2txt())
    .pipe(gulp.dest('build/'))
})

// start up browserSync
gulp.task('browserSync', (done) => {
  browserSync.init({
    server: 'working'
  })
  done()
})

// reload browserSync
gulp.task('reload', (done) => {
  browserSync.reload()
  done()
})

// watch scss and html files for changes
gulp.task('watchSassAndHtml', () => {
  gulp.watch('working/scss/*.scss', gulp.parallel('sass', 'reload'))
  gulp.watch('working/*.html', gulp.series('reload'))
})

// compress images
gulp.task('img', (done) => {
  return gulp.src('working/img/*')
    .pipe(imagemin())
    .pipe(gulp.dest('build/img'))
})

const sendmail = (done) => {
  const credentials = require('./credentials.json')
  const mg = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY || credentials.mailgun.key })
  const information = require('./working/information.json')
  const html = new Promise((resolve, reject) => {
    fs.readFile('./build/index.html', 'utf-8', (err, data) => {
      if (err) throw Error(err)
      if (data.length > 0) {
        let newData = data.replace(/img\/(.*?)"/gi, 'cid:$1"')
        resolve(newData)
      } else {
        reject('Empty file')
      }
    })
  })
  const text = new Promise((resolve, reject) => {
    fs.readFile('./build/index.txt', 'utf-8', (err, data) => {
      if (err) throw Error(err)
      if (data.length > 0) {
        resolve(data)
      } else {
        reject('Empty file')
      }
    })
  })
  const images = new Promise((resolve, reject) => {
    // read the path to the images and return a readStream for each file
    fs.readdir(path.join(__dirname, 'working', 'img'), (err, files) => {
      console.log(files)
      if (err) reject(err)
      const streamOfImages = files.map(img => {
        return fs.createReadStream(path.join(__dirname, 'working', 'img', img))
      })
      resolve(streamOfImages)
    })
  })
  const attachments = new Promise((resolve, reject) => {
    // read the path to the attachments and return a readStream for each file
    fs.readdir(path.join(__dirname, 'working', 'attachments'), (err, files) => {
      if (err) throw Error(err)
      const streamOfFiles = files
      .filter(file => {
        // .gitkeep should not be sent
        return file !== '.gitkeep'
      })
      .map(file => {
        return fs.createReadStream(path.join(__dirname, 'working', 'attachments', file))
      })
      resolve(streamOfFiles)
    })
  })
  Promise.all([html, text, images, attachments]).then(emails => {
    let [html, text, images, attachments] = emails
    mg.messages.create(credentials.mailgun.domain, {
      from: `${information.senderName} ${information.senderEmail}`,
      to: information.recipient,
      subject: information.subject,
      text: text,
      html: html,
      inline: images,
      attachment: attachments
    })
      .then(msg => {
        console.log(msg)
        done()
      }
      ) // logs response data
      .catch(err => {
        console.log(err)
        done()
      }) // logs any error
    done()
  }).catch(err => {
    console.log(err)
    done()
  })
}

gulp.task('email', gulp.series('css', 'premailer', 'txt', sendmail))
gulp.task('build', gulp.series('css', 'premailer', 'txt', 'img'))
gulp.task('serve', gulp.series('sass', gulp.parallel('browserSync', 'watchSassAndHtml')))

module.exports = {
  replaceLinks,
  addParameters,
  createParameterString,
  createHtml,
  sendmail
}
<commit_msg>add custom replace string<commit_after>const gulp = require('gulp')
const browserSync = require('browser-sync').create()
const sass = require('gulp-sass')
const plumber = require('gulp-plumber')
const gutil = require('gulp-util')
const fs = require('fs')
const juice = require('juice')
const path = require('path')
const information = require(path.join(__dirname, 'working/information.json'))
const html2txt = require('gulp-html2txt')
const cheerio = require('cheerio')
const mailgun = require('mailgun.js')
const imagemin = require('gulp-imagemin')
const postcss = require('gulp-postcss')
const uncss = require('postcss-uncss')

// Compile sass into CSS & auto-inject into browsers
gulp.task('sass', (done) => {
  return gulp.src('working/scss/*.scss')
    // plumber is used so that the watch task does not break when there is some wrong code
    .pipe(plumber(function (error) {
      gutil.beep()
      console.log(error)
      this.emit('end')
    }))
    .pipe(sass())
    .pipe(gulp.dest('working/css'))
    .pipe(browserSync.stream())
})

// Compile sass into CSS, remove unneeded CSS and dump it into the working folder for use in build process
// it is not neccessary to create a build CSS file just for the purpose of building the final email
gulp.task('css', () => {
  var plugins = [
    uncss({
      html: ['working/index.html']
    })
  ]
  return gulp.src('working/scss/*.scss')
    .pipe(plumber(function (error) {
      gutil.beep()
      console.log(error)
      this.emit('end')
    }))
    .pipe(sass())
    .pipe(postcss(plugins))
    .pipe(gulp.dest('working/css'))
})

/**
* replaces each instance of a replacelist key template in the string for the replacelist value
* @param {string}
* @param {object}
* @return {string}
*/
const replaceLinks = (str, replacelist = {}, customUrl = '') => {
  // there has to be a string to pass through even if replacelist is empty
  if (str.length < 1) throw new Error('File is empty')
  // if there is no replacelist or it is empty, pass through the value
  let html = str
  if (Object.keys(replacelist).length !== 0) {
    for (const key of Object.keys(replacelist)) {
      let replacestring = new RegExp(`#{${key}}`, 'gi')
      if (customUrl) {
        let url = customUrl.replace(/(#{url})/g, replacelist[key])
        html = html.replace(replacestring, url)
      } else {
        html = html.replace(replacestring, replacelist[key])
      }
    }
    let basicReg = /#{(.*?)}/g
    let els = html.match(basicReg)
    if (els !== null) {
      // find all matches and filter out the fluff
      // TODO think about if a set would be more appropriate as it would only list the unique values
      /*
      let allElements = new Set(els.map(x => x.replace('#{','').replace('}', '')))
      console.log(`Warning: there are still ${allElements.size} elements not replaced: ${[...allElements]}'`)
      */
      let allElements = els.map(x => x.replace('#{', '').replace('}', ''))
      console.log(`Warning: there are still ${allElements.length} instances not replaced: ${allElements}`)
    }
    return html
  } else {
    console.log('Warning: no links are provided, html goes through as is.')
    return str
  }
}

/**
* creates a url parameter string from the links object given
* @param {object}
* @return {string}
*/
const createParameterString = (parameters) => {
  let availableUtmKeys = ['source', 'medium', 'name', 'term', 'content']
  let paramsArr = []

  // check if the parameters exist, otherwise don't bother
  if (parameters.utm || parameters.custom) {
    // loop through all the utm and custom keys
    for (const paramKeys of Object.keys(parameters)) {
      // loop through the inner keys of utm and custom
      for (const key of Object.keys(parameters[paramKeys])) {
        // don't add if it is empty or if it is not a string or number
        if (parameters[paramKeys][key] && (typeof parameters[paramKeys][key] === 'string' || typeof parameters[paramKeys][key] === 'number')) {
          // only add the keys that are valid utm keys, ignore the ones that are not correct
          // IGNORE OR ERROR?
          if (paramKeys === 'utm' && availableUtmKeys.indexOf(key) !== -1) {
            paramsArr.push(`utm_${key}=${parameters[paramKeys][key]}`)
          } else if (paramKeys !== 'utm') { // if the keys are not utm, you are free to add them
            paramsArr.push(`${key}=${parameters[paramKeys][key]}`)
          }
        }
      }
    }
  }
  // joining all parameters in a string
  // if there are no parameters, create an empty string
  let paramsString = paramsArr.length !== 0 ? `?${paramsArr.join('&')}` : ''
  return paramsString
}

/**
* replaces each instance of a href atrribute in the first argument for the values provided in the second argument
* @param {string}
* @param {string}
* @param {function}
* @return {string}
*/
const addParameters = (str, parameters = '', cheerio) => {
  // check if the createParameterString returns a non-empty string
  // to avoid all the work that is not needed
  if (parameters) {
    // load the cheerio instance for appending links
    const $ = cheerio.load(str, { decodeEntities: false })
    // create the parameter string that will be appended to each href

    // add the parameters to each href in the email
    $('a').each(function (i, el) {
      let origHref = $(this).attr('href')
      // if the split method returns an array that is longer than 1 element, it means that there is an existing parameter in it
      // append the parameters only if there is no existing parameters
      if (origHref.split('?').length === 1) {
        $(this).attr('href', origHref + parameters)
      }
    })
    return $.html()
  } else {
    // if there is nothing available, return as is
    return str
  }
}

/**
* replaces each instance of a href atrribute in the first argument for the values provided in the second argument
* @param {string}
* @param {string}
* @return {string}
*/
const createHtml = (html, cssPath, addParameters, createParameterString, cheerio) => {
  return new Promise((resolve, reject) => {
    // replace the file path in the html file so that juice can find it, looking from the root of the project
    // replace the path for images since juice needs all files to be present
    let email = html.replace('<link rel="stylesheet" href="css/styles.css">', `<link rel="stylesheet" href="${cssPath}">`).replace('img/', 'working/img/')
    juice.juiceResources(email, { preserveMediaQueries: true, applyStyleTags: true }, (err, html) => {
      if (err) reject(err)
      // just create the html that will be used
      resolve(html)
    })
  })
}
// addParameters(replaceLinks(html, information.links), createParameterString(information.parameters), cheerio)
gulp.task('premailer', (done) => {
  fs.readFile('working/index.html', 'utf-8', (err, html) => {
    if (err) throw err
    createHtml(html, 'working/css/styles.css', addParameters, createParameterString, cheerio).then(html => {
      fs.writeFile('build/index.html', html.replace('working/img/', 'img/'), 'utf8', err => {
        if (err) {
          if (err.code === 'ENOENT') {
            fs.mkdir('build', err => {
              if (err) console.log(err)
              fs.writeFile('build/index.html', html.replace('working/img/', 'img/'), 'utf8', err => {
                if (err) console.log(err)
                done()
              })
            })
          }
        } else {
          done()
        }
      })
    }).catch(e => {
      console.log(e)
    })
  })
})

// convert html to txt
gulp.task('txt', () => {
  return gulp.src('build/index.html')
    .pipe(html2txt())
    .pipe(gulp.dest('build/'))
})

// start up browserSync
gulp.task('browserSync', (done) => {
  browserSync.init({
    server: 'working'
  })
  done()
})

// reload browserSync
gulp.task('reload', (done) => {
  browserSync.reload()
  done()
})

// watch scss and html files for changes
gulp.task('watchSassAndHtml', () => {
  gulp.watch('working/scss/*.scss', gulp.parallel('sass', 'reload'))
  gulp.watch('working/*.html', gulp.series('reload'))
})

// compress images
gulp.task('img', (done) => {
  return gulp.src('working/img/*')
    .pipe(imagemin())
    .pipe(gulp.dest('build/img'))
})

const sendmail = (done) => {
  const credentials = require('./credentials.json')
  const mg = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY || credentials.mailgun.key })
  const information = require('./working/information.json')
  const html = new Promise((resolve, reject) => {
    fs.readFile('./build/index.html', 'utf-8', (err, data) => {
      if (err) throw Error(err)
      if (data.length > 0) {
        let newData = data.replace(/img\/(.*?)"/gi, 'cid:$1"')
        resolve(newData)
      } else {
        reject('Empty file')
      }
    })
  })
  const text = new Promise((resolve, reject) => {
    fs.readFile('./build/index.txt', 'utf-8', (err, data) => {
      if (err) throw Error(err)
      if (data.length > 0) {
        resolve(data)
      } else {
        reject('Empty file')
      }
    })
  })
  const images = new Promise((resolve, reject) => {
    // read the path to the images and return a readStream for each file
    fs.readdir(path.join(__dirname, 'working', 'img'), (err, files) => {
      console.log(files)
      if (err) reject(err)
      const streamOfImages = files.map(img => {
        return fs.createReadStream(path.join(__dirname, 'working', 'img', img))
      })
      resolve(streamOfImages)
    })
  })
  const attachments = new Promise((resolve, reject) => {
    // read the path to the attachments and return a readStream for each file
    fs.readdir(path.join(__dirname, 'working', 'attachments'), (err, files) => {
      if (err) throw Error(err)
      const streamOfFiles = files
      .filter(file => {
        // .gitkeep should not be sent
        return file !== '.gitkeep'
      })
      .map(file => {
        return fs.createReadStream(path.join(__dirname, 'working', 'attachments', file))
      })
      resolve(streamOfFiles)
    })
  })
  Promise.all([html, text, images, attachments]).then(emails => {
    let [html, text, images, attachments] = emails
    mg.messages.create(credentials.mailgun.domain, {
      from: `${information.senderName} ${information.senderEmail}`,
      to: information.recipient,
      subject: information.subject,
      text: text,
      html: html,
      inline: images,
      attachment: attachments
    })
      .then(msg => {
        console.log(msg)
        done()
      }
      ) // logs response data
      .catch(err => {
        console.log(err)
        done()
      }) // logs any error
    done()
  }).catch(err => {
    console.log(err)
    done()
  })
}

gulp.task('links', (done) => {
  fs.readFile('./build/index.html', 'utf-8', (err, data) => {
    if (err) console.log(err)
    fs.writeFile('./build/index.html', addParameters(replaceLinks(data, information.links), createParameterString(information.parameters), cheerio), 'utf-8', err => {
      if (err) console.log(err)
      done()
    })
  })
})

gulp.task('email', gulp.series('css', 'premailer', 'txt', sendmail))
gulp.task('build', gulp.series('css', 'premailer', 'txt', 'img', 'links'))
gulp.task('serve', gulp.series('sass', gulp.parallel('browserSync', 'watchSassAndHtml')))

module.exports = {
  replaceLinks,
  addParameters,
  createParameterString,
  createHtml,
  sendmail
}
<|endoftext|>


<commit_before>var gulp = require('gulp'),
	plugins = require('gulp-load-plugins')();

plugins.fs = require('fs');
plugins.panini = require('panini');

// Load the version information
var version = plugins.fs.readFileSync('./less/version.less', 'utf8');

/**
 * Compile the source less generating both normal and minified CSS
 *
 * @param string src The source to compile.
 * @param string dst The target file name.
 * @param string dstMin The target file name for the minified version.
 * @returns gulp
 */
function cssTask(src, dst, dstMin) {
	return gulp.src(src)
		.pipe(plugins.less())
		.pipe(plugins.rename(dst))
		.pipe(gulp.dest('./css/'))

		.pipe(plugins.cleanCss())
		.pipe(plugins.insert.prepend(version))
		.pipe(plugins.rename(dstMin))
		.pipe(gulp.dest('./css/'));
}

gulp.task('blue.pink', function() {
	var theme = 'blue.pink';
	return cssTask('./less/themes/fusionCSS.' + theme + '.less', 'fusioncss.' + theme + '.css', 'fusioncss.' + theme + '.min.css');
});

gulp.task('bluegrey.red', function() {
	var theme = 'bluegrey.red';
	return cssTask('./less/themes/fusionCSS.' + theme + '.less', 'fusioncss.' + theme + '.css', 'fusioncss.' + theme + '.min.css');
});

// Build the material themes
gulp.task('less', [
	'blue.pink',
	'bluegrey.red'
]);

gulp.task('js', function() {
	return gulp.src([
		'./js/src/fusionCSS.js'
	])
		.pipe(plugins.concat('fusionCSS.js'))
		.pipe(plugins.uglify())
		.pipe(plugins.insert.prepend(version))
		.pipe(gulp.dest('./js/'));
});

gulp.task('clean', function() {
	return del([
		'./css/*.css',
		'./js/*.js',
		'./docs/*.html'
	]);
});

gulp.task('docs', function() {
	gulp.src('./docs_src/*.html')
		.pipe(plugins.panini({
			root: './docs_src/',
			layouts: './docs_src/layouts/',
			partials: './docs_src/partials/',
			helpers: './docs_src/helpers/',
			data: './docs_src/data/'
		}))
		.pipe(gulp.dest('docs'));
});

gulp.task('watch', function() {
	gulp.watch([
		'./less/*.less',
		'./less/themes/*.less'
	], ['less']);
	gulp.watch('./js/src/*.js', ['js']);
});

gulp.task('default', ['less', 'js']);

<commit_msg>Tidy build process<commit_after>var gulp = require('gulp'),
	plugins = require('gulp-load-plugins')();

plugins.fs = require('fs');
plugins.del = require('del');
plugins.panini = require('panini');

// Load the version information
var version = plugins.fs.readFileSync('./less/version.less', 'utf8'),
	themeTaskList = [];

/**
 * Get a list of the available themes and create the tasks
 */
var themeList = plugins.fs.readdirSync('./less/themes/');
themeList.forEach(function (theme) {

	// Create the names for the CSS and minified CSS files
	var cssfile = theme.toLowerCase().replace(/\.less$/, '.css'),
		minfile = theme.toLowerCase().replace(/\.less$/, '.min.css');

	// Create the task
	gulp.task(
		theme,
		function() {
			return gulp.src('./less/themes/' + theme)
				.pipe(plugins.less())
				.pipe(plugins.rename(cssfile))
				.pipe(gulp.dest('./css/'))

				.pipe(plugins.cleanCss())
				.pipe(plugins.insert.prepend(version))
				.pipe(plugins.rename(minfile))
				.pipe(gulp.dest('./css/'));
		}
	)
	themeTaskList.push(theme);
});

/**
 * Build JavaScript files
 */
gulp.task('js', function() {
	return gulp.src([
		'./js/src/fusionCSS.js'
	])
		.pipe(plugins.concat('fusionCSS.js'))
		.pipe(plugins.uglify())
		.pipe(plugins.insert.prepend(version))
		.pipe(gulp.dest('./js/'));
});

/**
 * Remove built files
 */
gulp.task('clean', function() {
	return plugins.del([
		'./css/*.css',
		'./js/*.js',
		'./docs/*.html'
	]);
});

/**
 * Build docs from doc sources
 */
gulp.task('docs', function() {
	gulp.src('./docs_src/*.html')
		.pipe(plugins.panini({
			root: './docs_src/',
			layouts: './docs_src/layouts/',
			partials: './docs_src/partials/',
			helpers: './docs_src/helpers/',
			data: './docs_src/data/'
		}))
		.pipe(gulp.dest('docs'));
});

/**
 * Watch files for changes
 */
gulp.task('watch', function() {
	gulp.watch([
		'./less/*.less',
		'./less/themes/*.less'
	], themeTaskList);
	gulp.watch('./js/src/*.js', ['js']);
});

/**
 * Default build task
 */
gulp.task('default', ['js'].concat(themeTaskList));
<|endoftext|>


<commit_before>var napi = require('napi-build-utils')
var gyp = require('./gyp')

function runGyp (opts, target, cb) {
  var args = ['node', 'index.js']
  if (opts.backend === 'node-ninja') {
    args.push('configure')
    args.push('build')
    args.push('--builddir=build/' + target)
  } else {
    args.push('rebuild')
  }
  if (napi.isNapiRuntime(opts.runtime)) {
    args.push('--napi_build_version=' + target)
  } else {
    args.push('--target=' + target)
  }
  args.push('--target_arch=' + opts.arch)
  if (opts.runtime === 'electron') {
    args.push('--runtime=electron')
    args.push('--dist-url=https://atom.io/download/electron')
  } else if (opts.runtime === 'node-webkit') {
    args.push('--runtime=node-webkit')
  } else if (opts.runtime === 'node') {
    // work around bug introduced in node 10's build https://github.com/nodejs/node-gyp/issues/1457
    args.push('--build_v8_with_gn=false')
  }
  if (opts.debug) args.push('--debug')

  if (opts.format) args.push('--', '--format', opts.format)

  gyp({
    gyp: opts.gyp,
    backend: opts.backend,
    log: opts.log,
    args: args,
    filter: function (command) {
      if (command.name === 'configure') {
        return configurePreGyp(command, opts)
      }
    }
  }, cb)
}

function configurePreGyp (command, opts) {
  var binary = opts.pkg.binary
  if (binary && binary.module_name) {
    command.args.push('-Dmodule_name=' + binary.module_name)
  }
  if (binary && binary.module_path) {
    command.args.push('-Dmodule_path=' + binary.module_path)
  }
}

module.exports = runGyp
<commit_msg>Work around build issue when targeting node 11<commit_after>var napi = require('napi-build-utils')
var gyp = require('./gyp')

function runGyp (opts, target, cb) {
  var args = ['node', 'index.js']
  if (opts.backend === 'node-ninja') {
    args.push('configure')
    args.push('build')
    args.push('--builddir=build/' + target)
  } else {
    args.push('rebuild')
  }
  if (napi.isNapiRuntime(opts.runtime)) {
    args.push('--napi_build_version=' + target)
  } else {
    args.push('--target=' + target)
  }
  args.push('--target_arch=' + opts.arch)
  if (opts.runtime === 'electron') {
    args.push('--runtime=electron')
    args.push('--dist-url=https://atom.io/download/electron')
  } else if (opts.runtime === 'node-webkit') {
    args.push('--runtime=node-webkit')
  } else if (opts.runtime === 'node') {
    // work around bug introduced in node 10's build https://github.com/nodejs/node-gyp/issues/1457
    args.push('--build_v8_with_gn=false')
    // work around the same kind of bug for node 11
    args.push('--enable_lto=false')
  }
  if (opts.debug) args.push('--debug')

  if (opts.format) args.push('--', '--format', opts.format)

  gyp({
    gyp: opts.gyp,
    backend: opts.backend,
    log: opts.log,
    args: args,
    filter: function (command) {
      if (command.name === 'configure') {
        return configurePreGyp(command, opts)
      }
    }
  }, cb)
}

function configurePreGyp (command, opts) {
  var binary = opts.pkg.binary
  if (binary && binary.module_name) {
    command.args.push('-Dmodule_name=' + binary.module_name)
  }
  if (binary && binary.module_path) {
    command.args.push('-Dmodule_path=' + binary.module_path)
  }
}

module.exports = runGyp
<|endoftext|>


<commit_before>Package.describe({
  name: 'barbatus:ng2-pagination',
  version: '0.1.0',
  summary: 'Angular2 Pagination Components',
  git: 'https://github.com/michaelbromley/ng2-pagination',
  documentation: null
});

Package.onUse(function(api) {
  api.versionsFrom('1.2.0.1');

  api.use([
    'check@1.0.5',
    'underscore@1.0.4',
    'barbatus:angular2@0.7.1_1'
  ]);

  // api.addFiles([
  //   'typings/meteor-accounts.d.ts'
  // ], 'server');

  api.addAssets([
    'src/pagination-controls-cmp.html',
  ], 'client');

  api.addFiles([
    'main.css',
    'src/ng2-pagination.ts',
    'src/paginate-pipe.ts',
    'src/pagination-controls-cmp.ts',
    'src/pagination-service.ts',
    'system_config.js'
  ], 'client');
});

Package.onTest(function(api) {
  api.use([
    'tinytest',
    'sanjo:jasmine@0.18.0',
    'barbatus:ng2-pagination'
  ]);
});
<commit_msg>Minor change.<commit_after>Package.describe({
  name: 'barbatus:ng2-pagination',
  version: '0.1.0',
  summary: 'Angular2 Pagination Components',
  git: 'https://github.com/Urigo/Meteor-Angular2/tree/master/packages/ng2-pagination',
  documentation: null
});

Package.onUse(function(api) {
  api.versionsFrom('1.2.0.1');

  api.use([
    'check@1.0.5',
    'underscore@1.0.4',
    'barbatus:angular2@0.7.1_1'
  ]);

  // api.addFiles([
  //   'typings/meteor-accounts.d.ts'
  // ], 'server');

  api.addAssets([
    'src/pagination-controls-cmp.html',
  ], 'client');

  api.addFiles([
    'main.css',
    'src/ng2-pagination.ts',
    'src/paginate-pipe.ts',
    'src/pagination-controls-cmp.ts',
    'src/pagination-service.ts',
    'system_config.js'
  ], 'client');
});

Package.onTest(function(api) {
  api.use([
    'tinytest',
    'sanjo:jasmine@0.18.0',
    'barbatus:ng2-pagination'
  ]);
});
<|endoftext|>


<commit_before>import moment from 'moment';
import Posts from './collection.js';
import Users from 'meteor/nova:users';

//////////////////
// Link Helpers //
//////////////////

/**
 * @summary Return a post's link if it has one, else return its post page URL
 * @param {Object} post
 */
Posts.getLink = function (post, isAbsolute) {
  return !!post.url ? Telescope.utils.getOutgoingUrl(post.url) : this.getPageUrl(post, isAbsolute);
};
Posts.helpers({getLink: function (isAbsolute) {return Posts.getLink(this, isAbsolute);}});

/**
 * @summary Depending on the settings, return either a post's URL link (if it has one) or its page URL.
 * @param {Object} post
 */
Posts.getShareableLink = function (post) {
  return Telescope.settings.get("outsideLinksPointTo", "link") === "link" ? Posts.getLink(post) : Posts.getPageUrl(post, true);
};
Posts.helpers({getShareableLink: function () {return Posts.getShareableLink(this);}});

/**
 * @summary Whether a post's link should open in a new tab or not
 * @param {Object} post
 */
Posts.getLinkTarget = function (post) {
  return !!post.url ? "_blank" : "";
};
Posts.helpers({getLinkTarget: function () {return Posts.getLinkTarget(this);}});

/**
 * @summary Get URL of a post page.
 * @param {Object} post
 */
Posts.getPageUrl = function(post, isAbsolute){
  isAbsolute = typeof isAbsolute === "undefined" ? false : isAbsolute; // default to false
  var prefix = isAbsolute ? Telescope.utils.getSiteUrl().slice(0,-1) : "";
  return `${prefix}/posts/${post._id}/${post.slug}`;
};
Posts.helpers({getPageUrl: function (isAbsolute) {return Posts.getPageUrl(this, isAbsolute);}});

///////////////////
// Other Helpers //
///////////////////

/**
 * @summary Get a post author's name
 * @param {Object} post
 */
Posts.getAuthorName = function (post) {
  var user = Meteor.users.findOne(post.userId);
  if (user) {
    return user.getDisplayName();
  } else {
    return post.author;
  }
};
Posts.helpers({getAuthorName: function () {return Posts.getAuthorName(this);}});

/**
 * @summary Get default status for new posts.
 * @param {Object} user
 */
Posts.getDefaultStatus = function (user) {
  var hasAdminRights = typeof user === 'undefined' ? false : Users.is.admin(user);
  if (hasAdminRights || !Telescope.settings.get('requirePostsApproval', false)) {
    // if user is admin, or else post approval is not required
    return Posts.config.STATUS_APPROVED;
  } else {
    return Posts.config.STATUS_PENDING;
  }
};

/**
 * @summary Check if a post is approved
 * @param {Object} post
 */
Posts.isApproved = function (post) {
  return post.status === Posts.config.STATUS_APPROVED;
};
Posts.helpers({isApproved: function () {return Posts.isApproved(this);}});

/**
 * @summary Check to see if post URL is unique.
 * We need the current user so we know who to upvote the existing post as.
 * @param {String} url
 */
Posts.checkForSameUrl = function (url) {

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

  if (typeof postWithSameLink !== 'undefined') {
    throw new Meteor.Error('603', 'this_link_has_already_been_posted', postWithSameLink._id);
  }
};

/**
 * @summary When on a post page, return the current post
 */
Posts.current = function () {
  return Posts.findOne("foo");
};

/**
 * @summary Check to see if a post is a link to a video
 * @param {Object} post
 */
Posts.isVideo = function (post) {
  return post.media && post.media.type === "video";
};
Posts.helpers({isVideo: function () {return Posts.isVideo(this);}});

/**
 * @summary Get the complete thumbnail url whether it is hosted on Embedly or on an external website, or locally in the app.
 * @param {Object} post
 */
Posts.getThumbnailUrl = (post) => {
  const thumbnailUrl = post.thumbnailUrl;
  if (!!thumbnailUrl) {
    return thumbnailUrl.indexOf('//') > -1 ? Telescope.utils.addHttp(thumbnailUrl) : Telescope.utils.getSiteUrl().slice(0,-1) + thumbnailUrl;
  }
};
Posts.helpers({ getThumbnailUrl() { return Posts.getThumbnailUrl(this); } });

///////////////////
// Users Helpers //
///////////////////

/**
 * @summary Check if a given user can view a specific post
 * @param {Object} user - can be undefined!
 * @param {Object} post
 */
Users.can.viewPost = function (user, post) {

  if (Users.is.admin(user)) {
    return true;
  } else {

    switch (post.status) {

      case Posts.config.STATUS_APPROVED:
        return Users.can.view(user);

      case Posts.config.STATUS_REJECTED:
      case Posts.config.STATUS_SPAM:
      case Posts.config.STATUS_PENDING:
        return Users.can.view(user) && Users.is.owner(user, post);

      case Posts.config.STATUS_DELETED:
        return false;

    }
  }
}
Users.helpers({canViewPost: function () {return Users.can.viewPost(this, post);}});
<commit_msg>add helpers for generating share URLs<commit_after>import moment from 'moment';
import Posts from './collection.js';
import Users from 'meteor/nova:users';

//////////////////
// Link Helpers //
//////////////////

/**
 * @summary Return a post's link if it has one, else return its post page URL
 * @param {Object} post
 */
Posts.getLink = function (post, isAbsolute = false, isRedirected = true) {
  const url = isRedirected ? Telescope.utils.getOutgoingUrl(post.url) : post.url;
  return !!post.url ? url : this.getPageUrl(post, isAbsolute);
};
Posts.helpers({getLink: function (isAbsolute) {return Posts.getLink(this, isAbsolute);}});

/**
 * @summary Depending on the settings, return either a post's URL link (if it has one) or its page URL.
 * @param {Object} post
 */
Posts.getShareableLink = function (post) {
  return Telescope.settings.get("outsideLinksPointTo", "link") === "link" ? Posts.getLink(post) : Posts.getPageUrl(post, true);
};
Posts.helpers({getShareableLink: function () {return Posts.getShareableLink(this);}});

/**
 * @summary Whether a post's link should open in a new tab or not
 * @param {Object} post
 */
Posts.getLinkTarget = function (post) {
  return !!post.url ? "_blank" : "";
};
Posts.helpers({getLinkTarget: function () {return Posts.getLinkTarget(this);}});

/**
 * @summary Get URL of a post page.
 * @param {Object} post
 */
Posts.getPageUrl = function(post, isAbsolute){
  isAbsolute = typeof isAbsolute === "undefined" ? false : isAbsolute; // default to false
  var prefix = isAbsolute ? Telescope.utils.getSiteUrl().slice(0,-1) : "";
  return `${prefix}/posts/${post._id}/${post.slug}`;
};
Posts.helpers({getPageUrl: function (isAbsolute) {return Posts.getPageUrl(this, isAbsolute);}});

///////////////////
// Other Helpers //
///////////////////

/**
 * @summary Get a post author's name
 * @param {Object} post
 */
Posts.getAuthorName = function (post) {
  var user = Meteor.users.findOne(post.userId);
  if (user) {
    return user.getDisplayName();
  } else {
    return post.author;
  }
};
Posts.helpers({getAuthorName: function () {return Posts.getAuthorName(this);}});

/**
 * @summary Get default status for new posts.
 * @param {Object} user
 */
Posts.getDefaultStatus = function (user) {
  var hasAdminRights = typeof user === 'undefined' ? false : Users.is.admin(user);
  if (hasAdminRights || !Telescope.settings.get('requirePostsApproval', false)) {
    // if user is admin, or else post approval is not required
    return Posts.config.STATUS_APPROVED;
  } else {
    return Posts.config.STATUS_PENDING;
  }
};

/**
 * @summary Check if a post is approved
 * @param {Object} post
 */
Posts.isApproved = function (post) {
  return post.status === Posts.config.STATUS_APPROVED;
};
Posts.helpers({isApproved: function () {return Posts.isApproved(this);}});

/**
 * @summary Check to see if post URL is unique.
 * We need the current user so we know who to upvote the existing post as.
 * @param {String} url
 */
Posts.checkForSameUrl = function (url) {

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

  if (typeof postWithSameLink !== 'undefined') {
    throw new Meteor.Error('603', 'this_link_has_already_been_posted', postWithSameLink._id);
  }
};

/**
 * @summary When on a post page, return the current post
 */
Posts.current = function () {
  return Posts.findOne("foo");
};

/**
 * @summary Check to see if a post is a link to a video
 * @param {Object} post
 */
Posts.isVideo = function (post) {
  return post.media && post.media.type === "video";
};
Posts.helpers({isVideo: function () {return Posts.isVideo(this);}});

/**
 * @summary Get the complete thumbnail url whether it is hosted on Embedly or on an external website, or locally in the app.
 * @param {Object} post
 */
Posts.getThumbnailUrl = (post) => {
  const thumbnailUrl = post.thumbnailUrl;
  if (!!thumbnailUrl) {
    return thumbnailUrl.indexOf('//') > -1 ? Telescope.utils.addHttp(thumbnailUrl) : Telescope.utils.getSiteUrl().slice(0,-1) + thumbnailUrl;
  }
};
Posts.helpers({ getThumbnailUrl() { return Posts.getThumbnailUrl(this); } });

/**
 * @summary Get URL for sharing on Twitter.
 * @param {Object} post
 */
Posts.getTwitterShareUrl = post => {
  const via = Telescope.settings.get("twitterAccount", null) ? `&via=${Telescope.settings.get("twitterAccount")}` : "";
  return `https://twitter.com/intent/tweet?text=${ encodeURIComponent(post.title) }%20${ encodeURIComponent(Posts.getLink(post, true)) }${via}`;
};
Posts.helpers({ getTwitterShareUrl() { return Posts.getTwitterShareUrl(this); } });

/**
 * @summary Get URL for sharing on Facebook.
 * @param {Object} post
 */
Posts.getFacebookShareUrl = post => {
  return `https://www.facebook.com/sharer/sharer.php?u=${ encodeURIComponent(Posts.getLink(post, true)) }`;
};
Posts.helpers({ getFacebookShareUrl() { return Posts.getFacebookShareUrl(this); } });

/**
 * @summary Get URL for sharing by Email.
 * @param {Object} post
 */
Posts.getEmailShareUrl = post => {
  const subject = `Interesting link: ${post.title}`;
  const body = `I thought you might find this interesting:

${post.title}
${Posts.getLink(post, true, false)}

(found via ${Telescope.settings.get("siteUrl")})
  `;
  return `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
};
Posts.helpers({ getEmailShareUrl() { return Posts.getEmailShareUrl(this); } });

///////////////////
// Users Helpers //
///////////////////

/**
 * @summary Check if a given user can view a specific post
 * @param {Object} user - can be undefined!
 * @param {Object} post
 */
Users.can.viewPost = function (user, post) {

  if (Users.is.admin(user)) {
    return true;
  } else {

    switch (post.status) {

      case Posts.config.STATUS_APPROVED:
        return Users.can.view(user);

      case Posts.config.STATUS_REJECTED:
      case Posts.config.STATUS_SPAM:
      case Posts.config.STATUS_PENDING:
        return Users.can.view(user) && Users.is.owner(user, post);

      case Posts.config.STATUS_DELETED:
        return false;

    }
  }
}
Users.helpers({canViewPost: function () {return Users.can.viewPost(this, post);}});
<|endoftext|>