<commit_before>var deleteFile, sendFile;

sendFile = function(file, toSummernote) {
    var data;
    data = new FormData;
    data.append('page[image]', file);
  return $.ajax({
        data: data,
        type: 'POST',
        url: 'upload',
        cache: false,
        contentType: false,
        processData: false,
    success: function(data) {
            var img;
            img = document.createElement('IMG');
            img.src = data.url;
            console.log(data);
            img.setAttribute('id', "sn-image-" + data.upload_id);
            return toSummernote.summernote('insertNode', img);

    }
  });
};

deleteFile = function(file_id) {
  return $.ajax({
        type: 'DELETE',
        url: "delete_upload" + file_id,
        cache: false,
        contentType: false,
        processData: false

  });
};

$(document).ready(function() {
  return $('[data-provider="summernote"]').each(function() {
    return $(this).summernote({
      height: 300,
      styleWithSpan: false,
      toolbar: [
            ['style', ['style']],
            ['font', ['bold', 'italic', 'underline', 'clear']],
            ['fontname', []],
            ['color', ['color']],
            ['para', ['ul', 'ol', 'paragraph']],
            ['height', ['height']],
            ['table', ['table']],
            ['insert', ['link', 'picture', 'hr']],
            ['view', ['fullscreen', 'codeview']],
            ['help', ['help']]

      ],
      callbacks: {
        onImageUpload: function(files) {
          return sendFile(files[0], $(this));
        },
        onMediaDelete: function(target, editor, editable) {
          var upload_id;
          upload_id = target[0].id.split('-').slice(-1)[0];
          console.log(upload_id);
          if (!!upload_id) {
            deleteFile(upload_id);

          }
          return target.remove();

        }
      }
    });
  });
});

<commit_msg>adding processing script<commit_after>var deleteFile, sendFile;

sendFile = function(file, toSummernote) {
    var data;
    data = new FormData;
    data.append('page[image]', file);
  return $.ajax({
        data: data,
        type: 'POST',
        url: 'upload',
        cache: false,
        contentType: false,
        processData: false,
    success: function(data) {
            var img;
            img = document.createElement('IMG');
            img.src = data.url;
            console.log(data);
            img.setAttribute('id', "sn-image-" + data.upload_id);
            return toSummernote.summernote('insertNode', img);

    }
  });
};

deleteFile = function(file_id) {
  return $.ajax({
        type: 'DELETE',
        url: "delete_upload" + file_id,
        cache: false,
        contentType: false,
        processData: false

  });
};

$(document).ready(function() {
  $('#update-transcript').on('click', function(e) {
    e.preventDefault();
    var id = $(this).attr('data-id');
    $.post('/admin/cms/transcripts/' + id + '/process_transcript', {}, function(data, textStatus, jqXHR) {
      $('#transcript-line-count').text(data.lines + ' lines in database');
    }, 'json');
  });

  return $('[data-provider="summernote"]').each(function() {
    return $(this).summernote({
      height: 300,
      styleWithSpan: false,
      toolbar: [
            ['style', ['style']],
            ['font', ['bold', 'italic', 'underline', 'clear']],
            ['fontname', []],
            ['color', ['color']],
            ['para', ['ul', 'ol', 'paragraph']],
            ['height', ['height']],
            ['table', ['table']],
            ['insert', ['link', 'picture', 'hr']],
            ['view', ['fullscreen', 'codeview']],
            ['help', ['help']]

      ],
      callbacks: {
        onImageUpload: function(files) {
          return sendFile(files[0], $(this));
        },
        onMediaDelete: function(target, editor, editable) {
          var upload_id;
          upload_id = target[0].id.split('-').slice(-1)[0];
          console.log(upload_id);
          if (!!upload_id) {
            deleteFile(upload_id);

          }
          return target.remove();

        }
      }
    });
  });
});

<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var util = require('util');
var mkdirp = require('mkdirp');
var mustache = require('mustache');

var config = require('../config.json');
var debug = require('../debug');

// Config
exports.meta = {
	name: 'logger',
	description: 'Logs all conversations on channels'
};

// Plugin private variables and functions
var logDir = 'log';
var me = util.format('%s/me', config.bot.nick); // For LOG

function getDirPath(channel) {
	return path.join('./',
		logDir,
		config.server.host,
		channel
	);

	// log/server/channel
}

function addLeadingZero(date) {
	if (date < 10)
		return '0' + date;

	return date;
}

function getFileName() {
	var date = new Date();
	var fileName = util.format('%s_%s_%s.txt',
		date.getUTCFullYear(),
		date.getUTCMonth() + 1, // Month +1 because Jan = 0, Feb = 1
		date.getUTCDate()
	);

	return fileName;
}

function writeToFile(channel, who, message) {
	var date = new Date();
	var fileName = getFileName();
	var text = util.format('%s:%s <%s> %s',
		addLeadingZero(date.getUTCHours()),
		addLeadingZero(date.getUTCMinutes()),
		who,
		message
	);

	debug.log(text);

	// Add new line to the file
	text += '\n';
	fs.appendFileSync(getDirPath(channel) + '/' + fileName, text);
}

exports.onPluginInit = function (bot) {

};

exports.onBotJoin = function (bot, channel) {
	// Create channel folder
	// If folder named channel doesn't exit create it
	var dirPath = getDirPath(channel);
	if (!fs.existsSync(dirPath)) {
		mkdirp.sync(dirPath);
	}

	writeToFile(channel, me, '[JOINED] to the channel ' + channel);
};

exports.onTopic = function (bot, channel, topic, user, message) {
	var pattern = '** Topic for {{&channel}}: {{&topic}} by {{&name}}';
	var data = {
		channel: channel,
		topic: topic,
		name: user.name
	};
	var output = mustache.render(pattern, data);

	debug.log(output);
	output += '\n';

	var fileName = getFileName();
	fs.appendFileSync(getDirPath(channel) + '/' + fileName, output);
};

exports.onUserJoin = function (bot, channel, user) {
	writeToFile(channel, user.fullName, '[JOINED] to the channel ' + channel);
};

exports.onMessage = function (bot, user, to, text, message) {
	writeToFile(to, user.nick, message);
};

exports.onUserNickChange = function (bot, user, channels) {
	for (var i in channels) {
		var pattern = '** {{&oldnick}} is known as {{&newnick}}';
		var data = {
			oldnick: user.oldnick,
			newnick: user.nick
		};
		var output = mustache.render(pattern, data);

		debug.log(output);
		output += '\n';

		var dirPath = getDirPath(channels[i]);
		var fileName = getFileName();
		fs.appendFileSync(dirPath + '/' + fileName, output);
	}
};

exports.onUserPart = function (bot, channel, user, reason) {
	var pattern = '** {{&fullName}} left the channel {{&channel}}';
	if (reason)
		pattern += ' ({{&reason}})';

	var data = {
		fullName: user.fullName,
		channel: channel,
		reason: reason
	};
	var output = mustache.render(pattern, data);

	debug.log(output);
	output += '\n';

	var dirPath = getDirPath(channel);
	var fileName = getFileName();
	fs.appendFileSync(dirPath + '/' + fileName, output);
};

exports.onUserQuit = function (bot, user, channels, reason) {
	for (var i in channels) {
		var pattern = '** {{&fullName}} left the channel {{&channel}}';
		if (reason)
			pattern += ' (Quit: {{&reason}})';

		var data = {
			fullName: user.fullName,
			channel: channels[i],
			reason: reason
		};
		var output = mustache.render(pattern, data);

		debug.log(output);
		output += '\n';

		var dirPath = getDirPath(channel[i]);
		var fileName = getFileName();
		fs.appendFileSync(dirPath + '/' + fileName, output);
	}
};<commit_msg>Some refractor<commit_after>var fs = require('fs');
var path = require('path');
var util = require('util');
var mkdirp = require('mkdirp');
var mustache = require('mustache');

var config = require('../config.json');
var debug = require('../debug');

// Config
exports.meta = {
	name: 'logger',
	description: 'Logs all conversations on channels'
};

// Plugin private variables and functions
var logDir = 'log';
var me = util.format('%s/me', config.bot.nick); // For LOG

function appendToFile(fileName, str) {
	var date = new Date();
	var time = util.format('%s:%s',
		addLeadingZero(date.getUTCHours()),
		addLeadingZero(date.getUTCMinutes())
	);
	var output = time + ' ' + str;

	debug.log(output);
	output += '\n';

	fs.appendFileSync(fileName, str);
}

function getDirPath(channel) {
	return path.join('./',
		logDir,
		config.server.host,
		channel
	);

	// log/server/channel
}

function addLeadingZero(date) {
	if (date < 10)
		return '0' + date;

	return date;
}

function getFileName() {
	var date = new Date();
	var fileName = util.format('%s_%s_%s.txt',
		date.getUTCFullYear(),
		date.getUTCMonth() + 1, // Month +1 because Jan = 0, Feb = 1
		date.getUTCDate()
	);

	return fileName;
}

function writeToFile(channel, who, message) {
	var date = new Date();
	var fileName = getFileName();
	var text = util.format('<%s> %s',
		who,
		message
	);

	appendToFile(getDirPath(channel) + '/' + fileName, text);
}

exports.onPluginInit = function (bot) {

};

exports.onBotJoin = function (bot, channel) {
	// Create channel folder
	// If folder named channel doesn't exit create it
	var dirPath = getDirPath(channel);
	if (!fs.existsSync(dirPath)) {
		mkdirp.sync(dirPath);
	}

	writeToFile(channel, me, '[JOINED] to the channel ' + channel);
};

exports.onTopic = function (bot, channel, topic, user, message) {
	var pattern = '** Topic for {{&channel}}: {{&topic}} by {{&name}}';
	var data = {
		channel: channel,
		topic: topic,
		name: user.name
	};
	var output = mustache.render(pattern, data);

	var fileName = getFileName();
	appendToFile(getDirPath(channel) + '/' + fileName, output);
};

exports.onUserJoin = function (bot, channel, user) {
	writeToFile(channel, user.fullName, '[JOINED] to the channel ' + channel);
};

exports.onMessage = function (bot, user, to, text, message) {
	writeToFile(to, user.nick, message);
};

exports.onUserNickChange = function (bot, user, channels) {
	for (var i in channels) {
		var pattern = '** {{&oldnick}} is known as {{&newnick}}';
		var data = {
			oldnick: user.oldnick,
			newnick: user.nick
		};
		var output = mustache.render(pattern, data);

		var dirPath = getDirPath(channels[i]);
		var fileName = getFileName();
		appendToFile(dirPath + '/' + fileName, output);
	}
};

exports.onUserPart = function (bot, channel, user, reason) {
	var pattern = '** {{&fullName}} left the channel {{&channel}}';
	if (reason)
		pattern += ' ({{&reason}})';

	var data = {
		fullName: user.fullName,
		channel: channel,
		reason: reason
	};
	var output = mustache.render(pattern, data);

	var dirPath = getDirPath(channel);
	var fileName = getFileName();
	appendToFile(dirPath + '/' + fileName, output);
};

exports.onUserQuit = function (bot, user, channels, reason) {
	for (var i in channels) {
		var pattern = '** {{&fullName}} left the channel {{&channel}}';
		if (reason)
			pattern += ' (Quit: {{&reason}})';

		var data = {
			fullName: user.fullName,
			channel: channels[i],
			reason: reason
		};
		var output = mustache.render(pattern, data);

		var dirPath = getDirPath(channel[i]);
		var fileName = getFileName();
		appendToFile(dirPath + '/' + fileName, output);
	}
};<|endoftext|>


<commit_before>function(doc){
    var i;
    if (doc.doc_type == "Group") {
    	for (i = 0; i < doc.users.length; i++) {
    		emit(doc.users[i], [doc.domain, doc.name]);
    	}
    }
}

<commit_msg>Javascript has a triple equals sign<commit_after>function(doc){
    var i;
    if (doc.doc_type === "Group") {
    	for (i = 0; i < doc.users.length; i++) {
    		emit(doc.users[i], [doc.domain, doc.name]);
    	}
    }
}

<|endoftext|>


<commit_before>module.exports = {
	"env": {
		"browser": true,
		"es6": true,
		"node": true
	},
	"extends": [
		"eslint:recommended",
		"plugin:@typescript-eslint/eslint-recommended",
		"plugin:@typescript-eslint/recommended",
		"prettier",
		"prettier/@typescript-eslint",
	], // prettier must be last in array to override other configs
	"parser": "@typescript-eslint/parser",
	"parserOptions": {
		"project": "tsconfig.json",
		"sourceType": "module"
	},
	"plugins": [
		"@typescript-eslint"
	],
	"rules": {
		"no-console": ["error", {allow : ["warn", "error"]}],
		"@typescript-eslint/no-empty-function": ["error", {allow: ["arrowFunctions"]}],
		"@typescript-eslint/camelcase": ["error", {properties: "never"}],
		"@typescript-eslint/no-unused-vars": ["error", { "vars": "all", "args": "none"}],
		"object-shorthand": ["error", "always"],

		// Do not apply inappropriate rules below
		"@typescript-eslint/no-inferrable-types": "off",
		"@typescript-eslint/no-use-before-define": "off",
		"@typescript-eslint/ban-ts-ignore": "off",

		// TODO fix and remove rules below
		"@typescript-eslint/explicit-function-return-type": "off",
		"@typescript-eslint/ban-types": "off",
		"@typescript-eslint/no-explicit-any": "off",
		"@typescript-eslint/no-var-requires": "off",
		"@typescript-eslint/consistent-type-assertions": "off",
	}
}
<commit_msg>Update enable no-inferrable-types rule<commit_after>module.exports = {
	"env": {
		"browser": true,
		"es6": true,
		"node": true
	},
	"extends": [
		"eslint:recommended",
		"plugin:@typescript-eslint/eslint-recommended",
		"plugin:@typescript-eslint/recommended",
		"prettier",
		"prettier/@typescript-eslint",
	], // prettier must be last in array to override other configs
	"parser": "@typescript-eslint/parser",
	"parserOptions": {
		"project": "tsconfig.json",
		"sourceType": "module"
	},
	"plugins": [
		"@typescript-eslint"
	],
	"rules": {
		"no-console": ["error", {allow : ["warn", "error"]}],
		"@typescript-eslint/no-empty-function": ["error", {allow: ["arrowFunctions"]}],
		"@typescript-eslint/camelcase": ["error", {properties: "never"}],
		"@typescript-eslint/no-unused-vars": ["error", { "vars": "all", "args": "none"}],
		"object-shorthand": ["error", "always"],

		// Do not apply inappropriate rules below
		"@typescript-eslint/no-use-before-define": "off",
		"@typescript-eslint/ban-ts-ignore": "off",

		// TODO fix and remove rules below
		"@typescript-eslint/explicit-function-return-type": "off",
		"@typescript-eslint/ban-types": "off",
		"@typescript-eslint/no-explicit-any": "off",
		"@typescript-eslint/no-var-requires": "off",
		"@typescript-eslint/consistent-type-assertions": "off",
	}
}
<|endoftext|>


<commit_before>function LoggerUtils(logger) {
  this._logger = logger;
};

LoggerUtils.instance = function(loggerConfig) {
  return new LoggerUtils(new Logger({
    server     : loggerConfig.server,
    defaultData: loggerConfig.data
  }));
};

LoggerUtils.submittedQuestionLogging = function(data,autoSave) {
  var loggerConfig = window.gon && window.gon.loggerConfig;
  if (!loggerConfig) {
    return;
  };

  var logger_utils = LoggerUtils.instance(loggerConfig);
  logger_utils._submittedQuestionLogging(data,autoSave);
};

LoggerUtils.submitArgblockLogging = function(data) {
  var loggerConfig = window.gon && window.gon.loggerConfig;
  if (!loggerConfig) {
    return;
  };

  var logger_utils = LoggerUtils.instance(loggerConfig);
  logger_utils._submitArgblockLogging(data);
};

LoggerUtils.logInteractiveEvents = function(iframe){
  var loggerConfig = window.gon && window.gon.loggerConfig;
  if (!loggerConfig) {
    return;
  };

  var logger_utils = LoggerUtils.instance(loggerConfig);
  logger_utils._logInteractiveEvents($(iframe)[0]);
};

LoggerUtils.enableLabLogging = function(iframeEl) {
  var labRpc = IframePhoneManager.getRpcEndpoint(iframeEl, 'lara-logging');
  labRpc.call({message: 'lara-logging-present'});
};

LoggerUtils.prototype.sequenceIndexLogging = function() {
  this._logger.log('open sequence index');
};

LoggerUtils.prototype.activityIndexLogging = function() {
  this._logger.log('open activity index');
};

LoggerUtils.prototype.interactivePageLogging = function(action) {
  this._logger.log('open activity page');
  this._logInteractiveEvents();
};

LoggerUtils.prototype.activitySummaryLogging = function() {
  this._logger.log('open activity report');
};
LoggerUtils.pageExitLogging = function() {
  var loggerConfig = window.gon && window.gon.loggerConfig;
  if (!loggerConfig) {
    return;
  }

  var logger_utils = LoggerUtils.instance(loggerConfig);
  logger_utils._pageExitLogging();
};
LoggerUtils.prototype._pageExitLogging = function() {
  this._logger.log('exit page');
};

LoggerUtils.prototype._getQuestionType = function(action) {
  if (action.indexOf("multiple_choice_answer") != -1) {
    return "multiple_choice_answer";
  } else if (action.indexOf("open_response_answer") != -1) {
    return "open_response_answer";
  } else if (action.indexOf("image_question_answer") != -1) {
    return "image_question_answer";
  } else return "embeddable_question";
};

LoggerUtils.prototype._submittedQuestionLogging = function(data,autoSave) {
  var data_object                = autoSave ? {event: 'answer saved'} : {event : 'submit answer'},
      question_type              = this._getQuestionType(data),
      question_id                = data.split('_').slice(-1)[0];

  data_object[question_type] = question_id;
  this._logger.log(data_object);
};

LoggerUtils.prototype._submitArgblockLogging = function(data) {
  this._logger.log({
    event  : "arg-block submit",
    page_id: data
  });
};

LoggerUtils.prototype._interactiveSimulationLogging = function() {
  // work around for bindig click event on cross site iframe
  var self  = this;
  $(window).on('blur', function() {
    $.each($('.interactive'),function(index,value){
      if ($(value).data().iframe_mouseover){
      	self._logger.log({
          event         : "clicked in the simulation window",
          interactive_id: $(value).data().id
        });
      }
    });
  });
  $('.interactive').on('mouseover', function() {
    $(this).data().iframe_mouseover = true;
  });
  $('.interactive').on('mouseout', function() {
    $(this).data().iframe_mouseover = false;
  });
};

LoggerUtils.prototype._logInteractiveEvents = function(iframe) {

  var handler = function(cmd) {
    var str;
    var index;
    var eventName;
    var eventValue;
    var parameters;

    if (cmd && cmd.action === 'logAction' && cmd.args) {
      str = cmd.args.formatStr;

      index = str.indexOf(':');

      if ( index >= 0) {
        eventName = str.slice(0, index);
        eventValue = str.slice(index + 1);
        try {
          parameters = JSON.parse(eventValue);
        } catch (e) {
          // noop
        }

        this._logger.log({
          event: eventName,
          event_value: eventValue,
          parameters: parameters,
          interactive_id: $(iframe).data().id,
          interactive_url: iframe.src
        });
      }
    }
  }.bind(this);

  // Setup handler for incoming logs.
  IframePhoneManager.getRpcEndpoint(iframe, 'lara-logging').handler = handler;
  // Notify Lab that logging is supported.
  LoggerUtils.enableLabLogging(iframe);
};

function Logger(options) {
  this._server = options.server;
  this._defaultData = options.defaultData;
}

Logger.prototype.log = function(data) {
  if (typeof(data) === 'string') {
    data = {event: data};
  }
  data.time = Date.now(); // millisecons
  this._post(data);
};

Logger.prototype._post = function(data) {
  var processedData = this._processData(data);
  $.ajax({
    url        : this._server,
    type       : "POST",
    crossDomain: true,
    data       : JSON.stringify(processedData),
    contentType: 'application/json'
    });
};

Logger.prototype._processData = function(data) {
  return $.extend(true, {}, this._defaultData, data);
};

var EmbeddableQuestionsLogging = function(question) {
  var loggerConfig = window.gon && window.gon.loggerConfig;
  if (!loggerConfig) {
    return;
  }

  this.question_type   = question.type;
  this.question_id     = question.id;
  this.question_dom_id = question.dom_id;

  this._logger = new Logger({
    server     : loggerConfig.server,
    defaultData: loggerConfig.data
  });

  this.bind_logger();
};

EmbeddableQuestionsLogging.prototype._generateQuestionData = function(event_type) {
  data                     = {};
  data['event']            = event_type;
  data[this.question_type] = this.question_id;
  return data;
};

EmbeddableQuestionsLogging.prototype.bind_logger = function() {
  var self     = this,
      focus_in = false;
  $("#" + this.question_dom_id).off('mousedown.logging').on('mousedown.logging',function(event){
    $(this).off('focusout.logging');
  });
  $("#" + this.question_dom_id).off('mouseup.logging').on('mouseup.logging',function(event){
    $(event.target).focus();
  });
  $("#" + this.question_dom_id).off('focusin.logging').on('focusin.logging', function(event) {
    if(!focus_in){
      self._logger.log(self._generateQuestionData('focus in'));
    }
    $(this).off('focusout.logging').on('focusout.logging', function(event) {
      self._logger.log(self._generateQuestionData('focus out'));
      $(this).off('focusout.logging');
      focus_in = false;
    });
    focus_in = true;
  });
};

$(document).ready(function() {
  // Logger configuration and metadata is provided in Rails controller through Gon gem.
  // See: https://github.com/gazay/gon
  var loggerConfig = window.gon && window.gon.loggerConfig;
  if (!loggerConfig) {
    // Exit if logger configuration isn't provided.
    return;
  }

  var logger_utils = LoggerUtils.instance(loggerConfig);

  switch (loggerConfig.action) {
    case 'sequences#show':
      logger_utils.sequenceIndexLogging();
      break;

    case 'lightweight_activities#show':
    case 'lightweight_activities#preview':
      logger_utils.activityIndexLogging();
      break;

    case 'lightweight_activities#summary':
      logger_utils.activitySummaryLogging();
      break;

    case 'interactive_pages#show':
    case 'interactive_pages#preview':
    case 'lightweight_activities#single_page':
      logger_utils.interactivePageLogging();
      break;
  }

});<commit_msg>Initialize interactiveSimulationLogging when page is loaded.<commit_after>function LoggerUtils(logger) {
  this._logger = logger;
};

LoggerUtils.instance = function(loggerConfig) {
  return new LoggerUtils(new Logger({
    server     : loggerConfig.server,
    defaultData: loggerConfig.data
  }));
};

LoggerUtils.submittedQuestionLogging = function(data,autoSave) {
  var loggerConfig = window.gon && window.gon.loggerConfig;
  if (!loggerConfig) {
    return;
  };

  var logger_utils = LoggerUtils.instance(loggerConfig);
  logger_utils._submittedQuestionLogging(data,autoSave);
};

LoggerUtils.submitArgblockLogging = function(data) {
  var loggerConfig = window.gon && window.gon.loggerConfig;
  if (!loggerConfig) {
    return;
  };

  var logger_utils = LoggerUtils.instance(loggerConfig);
  logger_utils._submitArgblockLogging(data);
};

LoggerUtils.logInteractiveEvents = function(iframe){
  var loggerConfig = window.gon && window.gon.loggerConfig;
  if (!loggerConfig) {
    return;
  };

  var logger_utils = LoggerUtils.instance(loggerConfig);
  logger_utils._logInteractiveEvents($(iframe)[0]);
};

LoggerUtils.enableLabLogging = function(iframeEl) {
  var labRpc = IframePhoneManager.getRpcEndpoint(iframeEl, 'lara-logging');
  labRpc.call({message: 'lara-logging-present'});
};

LoggerUtils.prototype.sequenceIndexLogging = function() {
  this._logger.log('open sequence index');
};

LoggerUtils.prototype.activityIndexLogging = function() {
  this._logger.log('open activity index');
};

LoggerUtils.prototype.interactivePageLogging = function(action) {
  this._logger.log('open activity page');
  this._interactiveSimulationLogging();
};

LoggerUtils.prototype.activitySummaryLogging = function() {
  this._logger.log('open activity report');
};
LoggerUtils.pageExitLogging = function() {
  var loggerConfig = window.gon && window.gon.loggerConfig;
  if (!loggerConfig) {
    return;
  }

  var logger_utils = LoggerUtils.instance(loggerConfig);
  logger_utils._pageExitLogging();
};
LoggerUtils.prototype._pageExitLogging = function() {
  this._logger.log('exit page');
};

LoggerUtils.prototype._getQuestionType = function(action) {
  if (action.indexOf("multiple_choice_answer") != -1) {
    return "multiple_choice_answer";
  } else if (action.indexOf("open_response_answer") != -1) {
    return "open_response_answer";
  } else if (action.indexOf("image_question_answer") != -1) {
    return "image_question_answer";
  } else return "embeddable_question";
};

LoggerUtils.prototype._submittedQuestionLogging = function(data,autoSave) {
  var data_object                = autoSave ? {event: 'answer saved'} : {event : 'submit answer'},
      question_type              = this._getQuestionType(data),
      question_id                = data.split('_').slice(-1)[0];

  data_object[question_type] = question_id;
  this._logger.log(data_object);
};

LoggerUtils.prototype._submitArgblockLogging = function(data) {
  this._logger.log({
    event  : "arg-block submit",
    page_id: data
  });
};

LoggerUtils.prototype._interactiveSimulationLogging = function() {
  // work around for bindig click event on cross site iframe
  var self  = this;
  $(window).on('blur', function() {
    $.each($('.interactive'),function(index,value){
      if ($(value).data().iframe_mouseover){
      	self._logger.log({
          event         : "clicked in the simulation window",
          interactive_id: $(value).data().id
        });
      }
    });
  });
  $('.interactive').on('mouseover', function() {
    $(this).data().iframe_mouseover = true;
  });
  $('.interactive').on('mouseout', function() {
    $(this).data().iframe_mouseover = false;
  });
};

LoggerUtils.prototype._logInteractiveEvents = function(iframe) {

  var handler = function(cmd) {
    var str;
    var index;
    var eventName;
    var eventValue;
    var parameters;

    if (cmd && cmd.action === 'logAction' && cmd.args) {
      str = cmd.args.formatStr;

      index = str.indexOf(':');

      if ( index >= 0) {
        eventName = str.slice(0, index);
        eventValue = str.slice(index + 1);
        try {
          parameters = JSON.parse(eventValue);
        } catch (e) {
          // noop
        }

        this._logger.log({
          event: eventName,
          event_value: eventValue,
          parameters: parameters,
          interactive_id: $(iframe).data().id,
          interactive_url: iframe.src
        });
      }
    }
  }.bind(this);

  // Setup handler for incoming logs.
  IframePhoneManager.getRpcEndpoint(iframe, 'lara-logging').handler = handler;
  // Notify Lab that logging is supported.
  LoggerUtils.enableLabLogging(iframe);
};

function Logger(options) {
  this._server = options.server;
  this._defaultData = options.defaultData;
}

Logger.prototype.log = function(data) {
  if (typeof(data) === 'string') {
    data = {event: data};
  }
  data.time = Date.now(); // millisecons
  this._post(data);
};

Logger.prototype._post = function(data) {
  var processedData = this._processData(data);
  $.ajax({
    url        : this._server,
    type       : "POST",
    crossDomain: true,
    data       : JSON.stringify(processedData),
    contentType: 'application/json'
    });
};

Logger.prototype._processData = function(data) {
  return $.extend(true, {}, this._defaultData, data);
};

var EmbeddableQuestionsLogging = function(question) {
  var loggerConfig = window.gon && window.gon.loggerConfig;
  if (!loggerConfig) {
    return;
  }

  this.question_type   = question.type;
  this.question_id     = question.id;
  this.question_dom_id = question.dom_id;

  this._logger = new Logger({
    server     : loggerConfig.server,
    defaultData: loggerConfig.data
  });

  this.bind_logger();
};

EmbeddableQuestionsLogging.prototype._generateQuestionData = function(event_type) {
  data                     = {};
  data['event']            = event_type;
  data[this.question_type] = this.question_id;
  return data;
};

EmbeddableQuestionsLogging.prototype.bind_logger = function() {
  var self     = this,
      focus_in = false;
  $("#" + this.question_dom_id).off('mousedown.logging').on('mousedown.logging',function(event){
    $(this).off('focusout.logging');
  });
  $("#" + this.question_dom_id).off('mouseup.logging').on('mouseup.logging',function(event){
    $(event.target).focus();
  });
  $("#" + this.question_dom_id).off('focusin.logging').on('focusin.logging', function(event) {
    if(!focus_in){
      self._logger.log(self._generateQuestionData('focus in'));
    }
    $(this).off('focusout.logging').on('focusout.logging', function(event) {
      self._logger.log(self._generateQuestionData('focus out'));
      $(this).off('focusout.logging');
      focus_in = false;
    });
    focus_in = true;
  });
};

$(document).ready(function() {
  // Logger configuration and metadata is provided in Rails controller through Gon gem.
  // See: https://github.com/gazay/gon
  var loggerConfig = window.gon && window.gon.loggerConfig;
  if (!loggerConfig) {
    // Exit if logger configuration isn't provided.
    return;
  }

  var logger_utils = LoggerUtils.instance(loggerConfig);

  switch (loggerConfig.action) {
    case 'sequences#show':
      logger_utils.sequenceIndexLogging();
      break;

    case 'lightweight_activities#show':
    case 'lightweight_activities#preview':
      logger_utils.activityIndexLogging();
      break;

    case 'lightweight_activities#summary':
      logger_utils.activitySummaryLogging();
      break;

    case 'interactive_pages#show':
    case 'interactive_pages#preview':
    case 'lightweight_activities#single_page':
      logger_utils.interactivePageLogging();
      break;
  }

});<|endoftext|>


<commit_before>function (doc) {
    // !code util/mvp.js
    if (isChildRegistrationForm(doc)) {
        var meta = doc.form.meta,
            indicators = get_indicators(doc),
            indicator_keys = [];

        var birth_weight = null,
            birth_weight_nan = false,
            reg_date;

        if (doc.dob || doc.dob_calc) {
            var dob_date = doc.dob_calc || doc.dob;
            reg_date = new Date(dob_date);
        } else {
            reg_date = new Date(meta.timeEnd);
        }

        try {
            birth_weight = (indicators.weight_at_birth && indicators.weight_at_birth.value) ? parseFloat(indicators.weight_at_birth.value) : 0;
        } catch (e) {
            birth_weight_nan = true;
        }

        indicator_keys.push("registration");
        if (indicators.delivered_in_facility
            && indicators.delivered_in_facility.value === 'yes') {
            indicator_keys.push("facility_delivery");
        }
        if (birth_weight >= 0 && !birth_weight_nan) {
            indicator_keys.push("birth_weight");
            if (birth_weight < 2.5) {
                indicator_keys.push("birth_weight low");
            }
        }

        emit_standard(doc, reg_date, indicator_keys, []);
    }
}
<commit_msg>add pregnancy outcome form<commit_after>function (doc) {
    // !code util/mvp.js
    if (isChildRegistrationForm(doc) ||
        isPregnancyCloseForm(doc)) {
        var meta = doc.form.meta,
            indicators = get_indicators(doc),
            indicator_keys = [];

        var birth_weight = null,
            birth_weight_nan = false,
            reg_date;

        if (doc.dob || doc.dob_calc) {
            var dob_date = doc.dob_calc || doc.dob;
            reg_date = new Date(dob_date);
        } else {
            reg_date = new Date(meta.timeEnd);
        }

        try {
            birth_weight = (indicators.weight_at_birth && indicators.weight_at_birth.value) ? parseFloat(indicators.weight_at_birth.value) : 0;
        } catch (e) {
            birth_weight_nan = true;
        }

        indicator_keys.push("registration");
        if (indicators.delivered_in_facility
            && indicators.delivered_in_facility.value === 'yes') {
            indicator_keys.push("facility_delivery");
        }
        if (birth_weight >= 0 && !birth_weight_nan) {
            indicator_keys.push("birth_weight");
            if (birth_weight < 2.5) {
                indicator_keys.push("birth_weight low");
            }
        }

        emit_standard(doc, reg_date, indicator_keys, []);
    }
}
<|endoftext|>


<commit_before>// import external

import ShortTermMemory from 'short-term-memory';

// helper functions

/**
 * @description mock impequid service provider data until it's finished
 */
export const fakeISPData = new Map();
fakeISPData.set('dns.smartfl.at', {
	url: 'https://dns.smartfl.at',
	returnUrl: 'https://dns.smartfl.at/api/authenticate/:token@:server',
	name: 'Smartflat DynDNS',
	author: {
		name: 'dodekeract',
		url: 'https://github.com/dodekeract'
	}
});

/**
 * @description add user data to session
 */
export function applyLogin (session, user) {
	session.name = user.name;
	session.email = user.email;
	session.apps = user.apps;
	session.id = user._id;
}

/**
 * @description support nginx proxy and direct access
 */
export function getIP (context) {
	return context.headers['x-real-ip'] || context.ip;
}

/**
 * @description get login information from a session
 */
export function getUser (context) {
	return {
		name: context.session.name,
		valid: !!context.session.name,
		id: context.session.id
	}
}

/**
 * @description for login-only-routes
 */
export function loginify (context, callback) {
	const user = getUser(context);
	if (user.valid) {
		return callback(user);
	} else {
		context.status = 401;
		context.body = {
			error: 'unauthorized'
		};
		// TODO investigate this hack
		return [];
	}
}

/**
 * @description create authenticate accept token list
 */
export const acceptTokenStore = new ShortTermMemory({
	duration: 120000
});
<commit_msg>added development server to fake isp data<commit_after>// import external

import ShortTermMemory from 'short-term-memory';

// helper functions

/**
 * @description mock impequid service provider data until it's finished
 */
export const fakeISPData = new Map();
fakeISPData.set('dns.smartfl.at', {
	url: 'https://dns.smartfl.at',
	returnUrl: 'https://dns.smartfl.at/api/authenticate/:token@:server',
	name: 'SmartFlat DynDNS',
	author: {
		name: 'dodekeract',
		url: 'https://github.com/dodekeract'
	}
});
fakeISPData.set('dodekeract.smartfl.at:8080', {
	url: 'http://dodekeract.smartfl.at:8080',
	returnUrl: 'https://dodekeract.smartfl.at:8080/api/authenticate/:token@:server',
	name: 'SmartFlat DynDNS Development Server',
	author: {
		name: 'dodekeract',
		url: 'https://github.com/dodekeract'
	}
});

/**
 * @description add user data to session
 */
export function applyLogin (session, user) {
	session.name = user.name;
	session.email = user.email;
	session.apps = user.apps;
	session.id = user._id;
}

/**
 * @description support nginx proxy and direct access
 */
export function getIP (context) {
	return context.headers['x-real-ip'] || context.ip;
}

/**
 * @description get login information from a session
 */
export function getUser (context) {
	return {
		name: context.session.name,
		valid: !!context.session.name,
		id: context.session.id
	}
}

/**
 * @description for login-only-routes
 */
export function loginify (context, callback) {
	const user = getUser(context);
	if (user.valid) {
		return callback(user);
	} else {
		context.status = 401;
		context.body = {
			error: 'unauthorized'
		};
		// TODO investigate this hack
		return [];
	}
}

/**
 * @description create authenticate accept token list
 */
export const acceptTokenStore = new ShortTermMemory({
	duration: 120000
});
<|endoftext|>


<commit_before>/**
 *  class Server
 *
 *  Easy to use server/middleware ideal for serving assets your assets:
 *
 *  - great for development, as it recompiles canged assets on-fly
 *  - great for production, as it caches results, and it can become as effecient
 *    as `staticCache` middleware (or even better) of `connect` module.
 *
 *
 *  ##### Examples
 *
 *      // development mode
 *      var srv = new Server(env);
 *
 *      // production mode
 *      var srv = new Server(env.index);
 *
 *      // production mode (restrictive)
 *      var files = ['app.js', 'app.css', 'logo.jpg'];
 *      manifest.compile(files, function (err, manifestData) {
 *        var srv = new Server(env.index, manifestData);
 *      });
 *
 *  You can use this server in your connect app (or as `request` listener of
 *  `http` server) like this:
 *
 *      app.use(function (req, res) {
 *        srv.handle(req, res);
 *      });
 *
 *      // there's a shorthand syntax as well:
 *
 *      app.use(mincer.createServer(env));
 **/

'use strict';

// stdlib
var http   = require('http');
var zlib   = require('zlib');
var url    = require('url');
var format = require('util').format;

// 3rd-party
var async = require('async');

// internal
var logger      = require('./logger');
var getter      = require('./common').getter;
var prop        = require('./common').prop;
var start_timer = require('./common').timer;

////////////////////////////////////////////////////////////////////////////////

/**
 *  new Server(environment[, manifest])
 *  - environment (Environment|Index)
 *  - manifest (Object): Data returned by [[Manifest#compile]]
 *
 *  If you provide `manifest`, then server will not even try to find files on
 *  FS unless they are specified in the `manifest`.
 **/
var Server = module.exports = function Server(environment, manifest) {
  prop(this, 'environment', environment);
  prop(this, 'manifest',    manifest);
};

// Retruns fingerprint from the pathname
var FINGERPRINT_RE = /-([0-9a-f]{32,40})\.[^.]+$/;
function get_fingerprint(pathname) {
  var m = FINGERPRINT_RE.exec(pathname);
  return m ? m[1] : null;
}

// Helper to write the code and end response
function end(res, code) {
  res.writeHead(code);
  res.end();
}

// Returns Etag value for `asset`
function etag(asset) {
  return '"' + asset.digest + '"';
}

// Returns true whenever If-None-Match header matches etag of `asset`
function is_etag_match(req, asset) {
  return etag(asset) === req.headers['if-none-match'];
}

// Returns whenever an asset can be gzipped or not
var COMPRESSABLE_MIME_RE = new RegExp([
  '^text/',
  '/json$',
  '/javascript$',
  '/svg+xml$',
  '/x-font-ttf$',
  '/x-font-otf$'
].join('|'));
function is_compressable(asset) {
  return COMPRESSABLE_MIME_RE.test(asset.contentType);
}

// Returns whenever an asset is text or not
var TEXT_MIME_RE = new RegExp([
  '^text/',
  '/json$',
  '/javascript$'
].join('|'));
function is_text(asset) {
  return TEXT_MIME_RE.test(asset.contentType);
}

// Tells whenever browser accepts gzip at all
function is_gzip_accepted(req) {
  var accept = req.headers['accept-encoding'] || '';
  return '*' === accept || 0 <= accept.indexOf('gzip');
}

// Tells whenever browser accepts gzip at all
function is_deflate_accepted(req) {
  var accept = req.headers['accept-encoding'] || '';
  return 0 <= accept.indexOf('deflate');
}

// small helper to set cmpressed buffer cache on asset.
// compressor is executed ONLY if asset has no cached property yet.
function set_compressed_cache(compressor, asset, name, callback) {
  if (undefined !== asset[name]) {
    callback();
    return;
  }

  if (500 > asset.length) {
    // asset is too small - compression will not give any profit.
    // mark asset as non-compressable.
    prop(asset, name, false);
    callback();
    return;
  }

  compressor(asset.buffer, function (err, buffer) {
    if (!err && !asset[name]) {
      // assign property only on success and if it was not yet assigned.
      // the last can happen due to ocasionally occured race conditions,
      // when same file that has not gzipped buffer yet was requested twice
      // so that calling compressor was scheduled twice
      // See: https://github.com/nodeca/mincer/issues/22
      prop(asset, name, buffer);
    }
    callback(err);
  });
}

// Returns log event structure.
//
function log_event(req, code, message, elapsed) {
  return {
    code:           code,
    message:        message,
    elapsed:        elapsed,
    url:            req.url,
    method:         req.method,
    headers:        req.headers,
    httpVersion:    req.httpVersion,
    remoteAddress:  req.connection.remoteAddress
  };
}

/** internal
 *  Server#log(level, event) -> Void
 *  - level (String): Event level
 *  - event (Object): Event data
 *
 *  This is an internal method that formats and writes messages using
 *  [[Mincer.logger]] and it fits almost 99% of cases. But if you want to
 *  integrate this [[Server]] into your existing application and have logs
 *  formatted in your way you can override this method.
 *
 *
 *  ##### Event
 *
 *  Event is an bject with following fields:
 *
 *  - **code** (Number): Status code
 *  - **message** (String): Message
 *  - **elapsed** (Number): Time elapsed in milliseconds
 *  - **url** (String): Request url. See `http.request.url`.
 *  - **method** (String): Request method. See `http.request.method`.
 *  - **headers** (Object): Request headers. See `http.request.headers`.
 *  - **httpVersion** (String): Request httpVersion. See `http.request.httpVersion`.
 **/
Server.prototype.log = function log(level, event) {
  logger[level](format('Served asset %s - %d %s (%dms)',
                       event.url, event.code, event.message, event.elapsed));
};

/**
 *  Server#compile(pathname, bundle, callback(err, asset)) -> Void
 *  - pathname (String)
 *  - bundle (Boolean)
 *  - callback (Function)
 *
 *  Finds and compiles given asset.
 **/
Server.prototype.compile = function compile(pathname, bundle, callback) {
  var asset = (this.manifest && !this.manifest.assets[pathname]) ? null
            : this.environment.findAsset(pathname, {bundle: !!bundle});

  if (!asset) {
    callback(/* err = undefined, asset = undefined */);
    return;
  }

  asset.compile(function (err/*, asset */) {
    if (err) {
      callback(err/*, asset = undefined */);
      return;
    }

    // got static asset without cached buffer - cache it
    if ('static' === asset.type && undefined === asset.__buffer__) {
      prop(asset, '__buffer__', asset.buffer);
    }

    // Cache gzipped buffer if asset is gzipable
    if (is_compressable(asset) && (undefined === asset.gzipped || undefined === asset.deflated)) {
      async.series([
        async.apply(set_compressed_cache, zlib.gzip, asset, 'gzipped'),
        async.apply(set_compressed_cache, zlib.deflate, asset, 'deflated')
      ], function (err) {
        callback(err, asset);
      });
      return;
    }

    // pass back to servant
    callback(err, asset);
  });
};

/**
 *  Server#handle(req, res) -> Void
 *  - req (http.ServerRequest)
 *  - res (hhtp.ServerResponse)
 *
 *  Hander function suitable for usage as server `request` event listenet or as
 *  middleware for TJ's `connect` module.
 *
 *
 *  ##### Exampple
 *
 *  var assetsSet
 **/
Server.prototype.handle = function handle(req, res) {
  var self        = this,
      timer       = start_timer(),
      pathname    = url.parse(req.url).pathname,
      bundle      = !/body=[1t]/.test(url.parse(req.url).query),
      fingerprint = get_fingerprint(pathname);

  try {
    pathname = decodeURIComponent(pathname.replace(/^\//, ''));
  } catch (err) {
    self.log('error', log_event(req, 400, 'Failed decode URL', timer.stop()));
    end(res, 400);
    return;
  }

  // forbid requests with `..` or NUL chars
  if (0 <= pathname.indexOf('..') || 0 <= pathname.indexOf("\u0000")) {
    self.log('error', log_event(req, 403, 'URL contains unsafe chars', timer.stop()));
    end(res, 403);
    return;
  }

  // ignore non-GET requests
  if ('GET' !== req.method && 'HEAD' !== req.method) {
    self.log('error', log_event(req, 403, 'HTTP method not allowed', timer.stop()));
    end(res, 403);
    return;
  }

  // remove fingerprint (digest) from URL
  if (fingerprint) {
    pathname = pathname.replace('-' + fingerprint, '');
  }

  // try to find and compile asset
  this.compile(pathname, bundle, function (err, asset) {
    var buffer, length;

    if (err) {
      err = err.message || err.toString();
      self.log('error', log_event(req, 500, 'Error compiling asset: ' + err, timer.stop()));
      end(res, 500);
      return;
    }

    // asset not found
    if (!asset) {
      self.log('error', log_event(req, 404, 'Not found', timer.stop()));
      end(res, 404);
      return;
    }

    //
    // Asset found. Sending headers for 200/304 responses:
    // http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-18#section-4.1
    //

    //
    // Ranges are not supported yet
    //

    res.removeHeader('Accept-Ranges');

    //
    // Mark for proxies, that we can return different content (plain & gzipped),
    // depending on specified (comma-separated) headers
    //

    res.setHeader('Vary', 'Accept-Encoding');

    //
    // Set caching headers
    //

    if (fingerprint) {
      // If the request url contains a fingerprint, set a long
      // expires on the response
      res.setHeader('Cache-Control', 'public, max-age=31536000');
    } else {
      // Otherwise set `must-revalidate` since the asset could be modified.
      res.setHeader('Cache-Control', 'public, max-age=0, must-revalidate');
    }

    res.setHeader('Date',           (new Date()).toUTCString());
    res.setHeader('Last-Modified',  asset.mtime.toUTCString());
    res.setHeader('ETag',           etag(asset));
    res.setHeader('Server',         'Nokla 1630');

    //
    // Check if asset's etag matches `if-none-match` header
    //

    if (is_etag_match(req, asset)) {
      self.log('info', log_event(req, 304, 'Not Modified', timer.stop()));
      end(res, 304);
      return;
    }

    // OK
    self.log('info', log_event(req, 200, 'OK', timer.stop()));

    //
    // Alter some headers for gzipped assets
    //

    if (asset.deflated && is_deflate_accepted(req)) {
      buffer = asset.deflated;
      length = asset.deflated.length;
      res.setHeader('Content-Encoding', 'deflate');
    }

    // gzip takes precedence if available

    if (asset.gzipped && is_gzip_accepted(req)) {
      buffer = asset.gzipped;
      length = asset.gzipped.length;
      res.setHeader('Content-Encoding', 'gzip');
    }

    //
    // Set content type and length headers
    // Force charset for text assets, to avoid problems with JS loaders
    //
    res.setHeader('Content-Type', asset.contentType + (is_text(asset) ? '; charset=UTF-8' : ''));
    res.setHeader('Content-Length', length || asset.length);

    res.statusCode = 200;

    if ('HEAD' === req.method) {
      res.end();
      return;
    }

    res.end(buffer || asset.buffer);
  });
};

/**
 *  Server.createServer(environment[, manifest]) -> Function
 *  - environment (Environment)
 *  - manifest (Object)
 *
 *  Returns a server function suitable to be used as `request` event handler of
 *  `http` Node library module or as `connect` middleware.
 *
 *
 *  ##### Example
 *
 *      // Using TJ's Connect module
 *      var app = connect();
 *      app.use('/assets/', Server.createServer(env));
 *
 *
 *  ##### See Also
 *
 *  - [[Server.new]]
 **/
Server.createServer = function (environment, manifest) {
  var srv = new Server(environment, manifest);
  return function (req, res) {
    return srv.handle(req, res);
  };
};
<commit_msg>Expose request object into log event<commit_after>/**
 *  class Server
 *
 *  Easy to use server/middleware ideal for serving assets your assets:
 *
 *  - great for development, as it recompiles canged assets on-fly
 *  - great for production, as it caches results, and it can become as effecient
 *    as `staticCache` middleware (or even better) of `connect` module.
 *
 *
 *  ##### Examples
 *
 *      // development mode
 *      var srv = new Server(env);
 *
 *      // production mode
 *      var srv = new Server(env.index);
 *
 *      // production mode (restrictive)
 *      var files = ['app.js', 'app.css', 'logo.jpg'];
 *      manifest.compile(files, function (err, manifestData) {
 *        var srv = new Server(env.index, manifestData);
 *      });
 *
 *  You can use this server in your connect app (or as `request` listener of
 *  `http` server) like this:
 *
 *      app.use(function (req, res) {
 *        srv.handle(req, res);
 *      });
 *
 *      // there's a shorthand syntax as well:
 *
 *      app.use(mincer.createServer(env));
 **/

'use strict';

// stdlib
var http   = require('http');
var zlib   = require('zlib');
var url    = require('url');
var format = require('util').format;

// 3rd-party
var async = require('async');

// internal
var logger      = require('./logger');
var getter      = require('./common').getter;
var prop        = require('./common').prop;
var start_timer = require('./common').timer;

////////////////////////////////////////////////////////////////////////////////

/**
 *  new Server(environment[, manifest])
 *  - environment (Environment|Index)
 *  - manifest (Object): Data returned by [[Manifest#compile]]
 *
 *  If you provide `manifest`, then server will not even try to find files on
 *  FS unless they are specified in the `manifest`.
 **/
var Server = module.exports = function Server(environment, manifest) {
  prop(this, 'environment', environment);
  prop(this, 'manifest',    manifest);
};

// Retruns fingerprint from the pathname
var FINGERPRINT_RE = /-([0-9a-f]{32,40})\.[^.]+$/;
function get_fingerprint(pathname) {
  var m = FINGERPRINT_RE.exec(pathname);
  return m ? m[1] : null;
}

// Helper to write the code and end response
function end(res, code) {
  res.writeHead(code);
  res.end();
}

// Returns Etag value for `asset`
function etag(asset) {
  return '"' + asset.digest + '"';
}

// Returns true whenever If-None-Match header matches etag of `asset`
function is_etag_match(req, asset) {
  return etag(asset) === req.headers['if-none-match'];
}

// Returns whenever an asset can be gzipped or not
var COMPRESSABLE_MIME_RE = new RegExp([
  '^text/',
  '/json$',
  '/javascript$',
  '/svg+xml$',
  '/x-font-ttf$',
  '/x-font-otf$'
].join('|'));
function is_compressable(asset) {
  return COMPRESSABLE_MIME_RE.test(asset.contentType);
}

// Returns whenever an asset is text or not
var TEXT_MIME_RE = new RegExp([
  '^text/',
  '/json$',
  '/javascript$'
].join('|'));
function is_text(asset) {
  return TEXT_MIME_RE.test(asset.contentType);
}

// Tells whenever browser accepts gzip at all
function is_gzip_accepted(req) {
  var accept = req.headers['accept-encoding'] || '';
  return '*' === accept || 0 <= accept.indexOf('gzip');
}

// Tells whenever browser accepts gzip at all
function is_deflate_accepted(req) {
  var accept = req.headers['accept-encoding'] || '';
  return 0 <= accept.indexOf('deflate');
}

// small helper to set cmpressed buffer cache on asset.
// compressor is executed ONLY if asset has no cached property yet.
function set_compressed_cache(compressor, asset, name, callback) {
  if (undefined !== asset[name]) {
    callback();
    return;
  }

  if (500 > asset.length) {
    // asset is too small - compression will not give any profit.
    // mark asset as non-compressable.
    prop(asset, name, false);
    callback();
    return;
  }

  compressor(asset.buffer, function (err, buffer) {
    if (!err && !asset[name]) {
      // assign property only on success and if it was not yet assigned.
      // the last can happen due to ocasionally occured race conditions,
      // when same file that has not gzipped buffer yet was requested twice
      // so that calling compressor was scheduled twice
      // See: https://github.com/nodeca/mincer/issues/22
      prop(asset, name, buffer);
    }
    callback(err);
  });
}

// Returns log event structure.
//
function log_event(req, code, message, elapsed) {
  return {
    code:           code,
    message:        message,
    elapsed:        elapsed,
    request:        req,
    url:            req.url,
    method:         req.method,
    headers:        req.headers,
    httpVersion:    req.httpVersion,
    remoteAddress:  req.connection.remoteAddress
  };
}

/** internal
 *  Server#log(level, event) -> Void
 *  - level (String): Event level
 *  - event (Object): Event data
 *
 *  This is an internal method that formats and writes messages using
 *  [[Mincer.logger]] and it fits almost 99% of cases. But if you want to
 *  integrate this [[Server]] into your existing application and have logs
 *  formatted in your way you can override this method.
 *
 *
 *  ##### Event
 *
 *  Event is an bject with following fields:
 *
 *  - **code** (Number): Status code
 *  - **message** (String): Message
 *  - **elapsed** (Number): Time elapsed in milliseconds
 *  - **url** (String): Request url. See `http.request.url`.
 *  - **method** (String): Request method. See `http.request.method`.
 *  - **headers** (Object): Request headers. See `http.request.headers`.
 *  - **httpVersion** (String): Request httpVersion. See `http.request.httpVersion`.
 **/
Server.prototype.log = function log(level, event) {
  logger[level](format('Served asset %s - %d %s (%dms)',
                       event.url, event.code, event.message, event.elapsed));
};

/**
 *  Server#compile(pathname, bundle, callback(err, asset)) -> Void
 *  - pathname (String)
 *  - bundle (Boolean)
 *  - callback (Function)
 *
 *  Finds and compiles given asset.
 **/
Server.prototype.compile = function compile(pathname, bundle, callback) {
  var asset = (this.manifest && !this.manifest.assets[pathname]) ? null
            : this.environment.findAsset(pathname, {bundle: !!bundle});

  if (!asset) {
    callback(/* err = undefined, asset = undefined */);
    return;
  }

  asset.compile(function (err/*, asset */) {
    if (err) {
      callback(err/*, asset = undefined */);
      return;
    }

    // got static asset without cached buffer - cache it
    if ('static' === asset.type && undefined === asset.__buffer__) {
      prop(asset, '__buffer__', asset.buffer);
    }

    // Cache gzipped buffer if asset is gzipable
    if (is_compressable(asset) && (undefined === asset.gzipped || undefined === asset.deflated)) {
      async.series([
        async.apply(set_compressed_cache, zlib.gzip, asset, 'gzipped'),
        async.apply(set_compressed_cache, zlib.deflate, asset, 'deflated')
      ], function (err) {
        callback(err, asset);
      });
      return;
    }

    // pass back to servant
    callback(err, asset);
  });
};

/**
 *  Server#handle(req, res) -> Void
 *  - req (http.ServerRequest)
 *  - res (hhtp.ServerResponse)
 *
 *  Hander function suitable for usage as server `request` event listenet or as
 *  middleware for TJ's `connect` module.
 *
 *
 *  ##### Exampple
 *
 *  var assetsSet
 **/
Server.prototype.handle = function handle(req, res) {
  var self        = this,
      timer       = start_timer(),
      pathname    = url.parse(req.url).pathname,
      bundle      = !/body=[1t]/.test(url.parse(req.url).query),
      fingerprint = get_fingerprint(pathname);

  try {
    pathname = decodeURIComponent(pathname.replace(/^\//, ''));
  } catch (err) {
    self.log('error', log_event(req, 400, 'Failed decode URL', timer.stop()));
    end(res, 400);
    return;
  }

  // forbid requests with `..` or NUL chars
  if (0 <= pathname.indexOf('..') || 0 <= pathname.indexOf("\u0000")) {
    self.log('error', log_event(req, 403, 'URL contains unsafe chars', timer.stop()));
    end(res, 403);
    return;
  }

  // ignore non-GET requests
  if ('GET' !== req.method && 'HEAD' !== req.method) {
    self.log('error', log_event(req, 403, 'HTTP method not allowed', timer.stop()));
    end(res, 403);
    return;
  }

  // remove fingerprint (digest) from URL
  if (fingerprint) {
    pathname = pathname.replace('-' + fingerprint, '');
  }

  // try to find and compile asset
  this.compile(pathname, bundle, function (err, asset) {
    var buffer, length;

    if (err) {
      err = err.message || err.toString();
      self.log('error', log_event(req, 500, 'Error compiling asset: ' + err, timer.stop()));
      end(res, 500);
      return;
    }

    // asset not found
    if (!asset) {
      self.log('error', log_event(req, 404, 'Not found', timer.stop()));
      end(res, 404);
      return;
    }

    //
    // Asset found. Sending headers for 200/304 responses:
    // http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-18#section-4.1
    //

    //
    // Ranges are not supported yet
    //

    res.removeHeader('Accept-Ranges');

    //
    // Mark for proxies, that we can return different content (plain & gzipped),
    // depending on specified (comma-separated) headers
    //

    res.setHeader('Vary', 'Accept-Encoding');

    //
    // Set caching headers
    //

    if (fingerprint) {
      // If the request url contains a fingerprint, set a long
      // expires on the response
      res.setHeader('Cache-Control', 'public, max-age=31536000');
    } else {
      // Otherwise set `must-revalidate` since the asset could be modified.
      res.setHeader('Cache-Control', 'public, max-age=0, must-revalidate');
    }

    res.setHeader('Date',           (new Date()).toUTCString());
    res.setHeader('Last-Modified',  asset.mtime.toUTCString());
    res.setHeader('ETag',           etag(asset));
    res.setHeader('Server',         'Nokla 1630');

    //
    // Check if asset's etag matches `if-none-match` header
    //

    if (is_etag_match(req, asset)) {
      self.log('info', log_event(req, 304, 'Not Modified', timer.stop()));
      end(res, 304);
      return;
    }

    // OK
    self.log('info', log_event(req, 200, 'OK', timer.stop()));

    //
    // Alter some headers for gzipped assets
    //

    if (asset.deflated && is_deflate_accepted(req)) {
      buffer = asset.deflated;
      length = asset.deflated.length;
      res.setHeader('Content-Encoding', 'deflate');
    }

    // gzip takes precedence if available

    if (asset.gzipped && is_gzip_accepted(req)) {
      buffer = asset.gzipped;
      length = asset.gzipped.length;
      res.setHeader('Content-Encoding', 'gzip');
    }

    //
    // Set content type and length headers
    // Force charset for text assets, to avoid problems with JS loaders
    //
    res.setHeader('Content-Type', asset.contentType + (is_text(asset) ? '; charset=UTF-8' : ''));
    res.setHeader('Content-Length', length || asset.length);

    res.statusCode = 200;

    if ('HEAD' === req.method) {
      res.end();
      return;
    }

    res.end(buffer || asset.buffer);
  });
};

/**
 *  Server.createServer(environment[, manifest]) -> Function
 *  - environment (Environment)
 *  - manifest (Object)
 *
 *  Returns a server function suitable to be used as `request` event handler of
 *  `http` Node library module or as `connect` middleware.
 *
 *
 *  ##### Example
 *
 *      // Using TJ's Connect module
 *      var app = connect();
 *      app.use('/assets/', Server.createServer(env));
 *
 *
 *  ##### See Also
 *
 *  - [[Server.new]]
 **/
Server.createServer = function (environment, manifest) {
  var srv = new Server(environment, manifest);
  return function (req, res) {
    return srv.handle(req, res);
  };
};
<|endoftext|>


<commit_before>var htmlparser2 = require(".."),
    assert = require("assert");

describe("API", function() {
    it("should load all modules", function() {
        var Stream = require("../lib/Stream.js");
        assert.strictEqual(htmlparser2.Stream, Stream, "should load module");
        assert.strictEqual(
            htmlparser2.Stream,
            Stream,
            "should load it again (cache)"
        );

        var ProxyHandler = require("../lib/ProxyHandler.js");
        assert.strictEqual(
            htmlparser2.ProxyHandler,
            ProxyHandler,
            "should load module"
        );
        assert.strictEqual(
            htmlparser2.ProxyHandler,
            ProxyHandler,
            "should load it again (cache)"
        );
    });

    it("should work without callbacks", function() {
        var p = new htmlparser2.Parser(null, {
            xmlMode: true,
            lowerCaseAttributeNames: true
        });

        p.end("<bar><![CDATA[]]]><?foo?><!bar><boo/>boohay");
        p.write("foo");

        //check for an error
        p.end();
        var err = false;
        p._cbs.onerror = function() {
            err = true;
        };
        p.write("foo");
        assert(err);
        err = false;
        p.end();
        assert(err);

        p.reset();

        //remove method
        p._cbs.onopentag = function() {};
        p.write("");

        //pause/resume
        var processed = false;
        p._cbs.ontext = function(t) {
            assert.equal(t, "foo");
            processed = true;
        };
        p.pause();
        p.write("foo");
        assert(!processed);
        p.resume();
        assert(processed);
        processed = false;
        p.pause();
        assert(!processed);
        p.resume();
        assert(!processed);
        p.pause();
        p.end("foo");
        assert(!processed);
        p.resume();
        assert(processed);
    });

    it("should update the position", function() {
        var p = new htmlparser2.Parser(null);

        p.write("foo");

        assert.equal(p.startIndex, 0);

        p.write("<bar>");

        assert.equal(p.startIndex, 3);
    });
});
<commit_msg>adding test<commit_after>var htmlparser2 = require(".."),
    assert = require("assert");

describe("API", function() {
    it("should load all modules", function() {
        var Stream = require("../lib/Stream.js");
        assert.strictEqual(htmlparser2.Stream, Stream, "should load module");
        assert.strictEqual(
            htmlparser2.Stream,
            Stream,
            "should load it again (cache)"
        );

        var ProxyHandler = require("../lib/ProxyHandler.js");
        assert.strictEqual(
            htmlparser2.ProxyHandler,
            ProxyHandler,
            "should load module"
        );
        assert.strictEqual(
            htmlparser2.ProxyHandler,
            ProxyHandler,
            "should load it again (cache)"
        );
    });

    it("should work without callbacks", function() {
        var p = new htmlparser2.Parser(null, {
            xmlMode: true,
            lowerCaseAttributeNames: true
        });

        p.end("<bar><![CDATA[]]]><?foo?><!bar><boo/>boohay");
        p.write("foo");

        //check for an error
        p.end();
        var err = false;
        p._cbs.onerror = function() {
            err = true;
        };
        p.write("foo");
        assert(err);
        err = false;
        p.end();
        assert(err);

        p.reset();

        //remove method
        p._cbs.onopentag = function() {};
        p.write("");

        //pause/resume
        var processed = false;
        p._cbs.ontext = function(t) {
            assert.equal(t, "foo");
            processed = true;
        };
        p.pause();
        p.write("foo");
        assert(!processed);
        p.resume();
        assert(processed);
        processed = false;
        p.pause();
        assert(!processed);
        p.resume();
        assert(!processed);
        p.pause();
        p.end("foo");
        assert(!processed);
        p.resume();
        assert(processed);
    });

    it("should update the position", function() {
        var p = new htmlparser2.Parser(null);

        p.write("foo");

        assert.equal(p.startIndex, 0);

        p.write("<bar>");

        assert.equal(p.startIndex, 3);
    });

    it("should support custom tokenizer", function() {
        function CustomTokenizer(options, cbs) {
            htmlparser2.Tokenizer.call(this, options, cbs);
            return this;
        }
        CustomTokenizer.prototype = Object.create(
            htmlparser2.Tokenizer.prototype
        );
        CustomTokenizer.prototype.constructor = CustomTokenizer;

        var p = new htmlparser2.Parser(
            {
                onparserinit(parser) {
                    assert(parser._tokenizer instanceof CustomTokenizer);
                }
            },
            { Tokenizer: CustomTokenizer }
        );
        p.write("");
        p.done();
    });
});
<|endoftext|>


<commit_before>Mousetrap.stopCallback = function(e, element, combo) {
  // stop for input, select, and textarea
  return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA';
};

var Editor = function() {
  this.toolbar = $('#toolbar');
  this.article = $('#editarea article');

  this.connectEvents();
  this.connectShortcuts();

  this.article.focus();
  this.initParagraph();
};

Editor.prototype = {
  events: {
    'click #save-button': 'saveArticle',
    'submit #urlname-modal form':  'saveUrlname',
    'click #draft-button': 'draft',
    'click #publish-button': 'publish',
    'keyup #editarea article': 'keyup',
    'keydown #editarea article': 'keydown',
    'mouseup #editarea article': 'detectState',
    'paste #editarea article': 'paste',
    'click #toolbar [data-command]': 'toolbarCommand'
  },

  connectEvents: function() {
    var _this = this;
    $.each(this.events, function(key, method) {
      var actions = key.split(' ');
      var event = actions.shift();
      var selector = actions.join(' ');
      $(selector).on(event, function(event) {
        _this[method].call(_this, event, this);
      });
    });
  },

  shortcuts: {
    'ctrl+b': 'bold',
    'ctrl+i': 'italic',
    'ctrl+d': 'strikeThrough',
    'ctrl+u': 'underline',
    'ctrl+l': 'createLink',
    'ctrl+shift+l': 'insertUnorderedList',
    'ctrl+shift+o': 'insertOrderedList',
    'ctrl+p': 'p',
    'ctrl+1': 'h1',
    'ctrl+2': 'h2',
    'ctrl+3': 'h3',
    'ctrl+4': 'h4',
    'ctrl+k': 'code',
    'ctrl+q': 'blockquote',
    'ctrl+s': 'saveArticle'
  },

  connectShortcuts: function() {
    var _this = this;
    $.each(this.shortcuts, function(key, method) {
      Mousetrap.bind(key, function(event) {
        event.preventDefault();
        _this[method].call(_this);
      });
    });
  },

  toolbarCommand: function(event, element) {
    event.preventDefault();
    this[$(element).data('command')].call(this);
    this.detectButton();
    this.detectBlocks();
  },

  detectState: function() {
    this.detectButton();
    this.detectBlocks();
  },

  detectButton: function() {
    var _this = this;

    _this.toolbar.find('[data-command]').each(function(index, element) {
      var command = $(element).data('command');
      if (document.queryCommandValue(command) !== 'true') {
        $(element).removeClass('actived');
      } else {
        if (command === 'bold' && /^h/.test(document.queryCommandValue('formatBlock'))) {
          $(element).removeClass('actived');
        } else {
          $(element).addClass('actived');
        }
      }
    });
  },

  detectBlocks: function() {
    var type = document.queryCommandValue('formatBlock');
    var text = this.toolbar.find('#format-block [data-command=' + type + ']').text();
    if (text === '') {
      text = this.toolbar.find('#format-block [data-command]:first').text();
    }
    this.toolbar.find('#format-block .toolbar-botton').text(text);
  },

  paste: function(event) {
    this.dirty = true;
  },

  bold: function() {
    this.exec('bold');
  },

  italic: function() {
    this.exec('italic');
  },

  strikeThrough: function() {
    this.exec('strikeThrough');
  },

  underline: function() {
    this.exec('underline');
  },

  insertOrderedList: function() {
    this.exec('insertOrderedList');
  },

  insertUnorderedList: function() {
    this.exec('insertUnorderedList');
  },

  createLink: function() {
    var url = prompt('Link url:', 'http://');
    if (url !== null && url !== '') {
      this.exec('createLink', url);
    }
  },

  p: function() {
    this.formatBlock('p');
  },

  formatHeader: function(type) {
    if (document.queryCommandValue('formatBlock') === type) {
      this.p();
    } else {
      this.formatBlock(type);
    }
  },

  h1: function() {
    this.formatHeader('h1');
  },

  h2: function() {
    this.formatHeader('h2');
  },

  h3: function() {
    this.formatHeader('h3');
  },

  h4: function() {
    this.formatHeader('h4');
  },

  code: function() {
    var selection = window.getSelection();
    var range = selection.getRangeAt(0);
    var rangeAncestor = range.commonAncestorContainer;
    var start, end, $contents;

    var $code = $(rangeAncestor).closest('code');

    if ($code.length) {
      // remove code
      if ($code.closest('pre').length) {
        // pre code
        this.splitCode($code);
        $contents = $code.contents();
        $code.closest('pre').replaceWith($contents);
        this.selectContents($contents);
      } else {
        // inline code
        $contents = $code.contents();
        $code.replaceWith($code.contents());
        this.selectContents($contents);
      }
    } else {
      // wrap code
      var isEmptyRange = (range.toString() === '');
      var isWholeBlock = (range.toString() === $(range.startContainer).closest('p, h1, h2, h3, h4').text());
      var hasBlock = (range.cloneContents().querySelector('p, h1, h2, h3, h4'));
      if (isEmptyRange || isWholeBlock || hasBlock) {
        // pre code
        start = $(range.startContainer).closest('article > *')[0];
        end = $(range.endContainer).closest('article > *')[0];
        range.setStartBefore(start);
        range.setEndAfter(end);
        $code = $('').html(range.extractContents());
        $pre = $('
').html($code);
        range.insertNode($pre[0]);
      } else {
        // inline code
        $code = $('').html(range.extractContents());
        range.insertNode($code[0]);
      }
      selection.selectAllChildren($code[0]);
      this.clearCode($code);
    }
  },

  clearCode: function(code) {
    code.find('p, h1, h2, h3, h4').each(function() {
      $(this).replaceWith($(this).text() + "\n");
    }).children().each(function() {
      $(this).replaceWith($(this).text());
    });
  },

  splitCode: function(code) {
    code.html($.map(code.text().split("\n"), function(line) {
      if (line !== '') {
        return $('
').text(line);
      }
    }));
  },

  selectContents: function(contents) {
    var selection = window.getSelection();
    var range = selection.getRangeAt(0);
    var start = contents.first()[0];
    var end = contents.last()[0];
    range.setStart(start, 0);
    range.setEnd(end, end.childNodes.length || end.length); // text node don't have childNodes
    console.log(range);
    selection.removeAllRanges();
    selection.addRange(range);
  },

  blockquote: function() {
    var selection = window.getSelection();
    var range = selection.getRangeAt(0);
    var rangeAncestor = range.commonAncestorContainer;
    var start, end;

    var $blockquote = $(rangeAncestor).closest('blockquote');
    if ($blockquote.length) {
      // remmove blockquote
      var $contents = $blockquote.contents();
      $blockquote.replaceWith($contents);
      this.selectContents($contents);
    } else {
      // wrap blockquote
      start = $(range.startContainer).closest('article > *')[0];
      end = $(range.endContainer).closest('article > *')[0];
      range.setStartBefore(start);
      range.setEndAfter(end);
      $blockquote = $('
');
      $blockquote.html(range.extractContents()).find('blockquote').each(function() {
        $(this).replaceWith($(this).html());
      });
      range.insertNode($blockquote[0]);
      selection.selectAllChildren($blockquote[0]);
      if ($blockquote.next().length === 0) {
        $('

').insertAfter($blockquote);
      }
    }
  },

  formatBlock: function(type) {
    this.exec('formatBlock', type);
  },

  exec: function(command, arg) {
    document.execCommand(command, false, arg);
  },

  keyup: function() {
    this.initParagraph();
    this.detectState();
    this.sanitize();
  },

  keydown: function(event) {
    this.stopEmptyBackspace(event);
  },

  initParagraph: function() {
    // chrome is empty and firefox is

    if (this.article.html() === '' || this.article.html() === '
') {
      this.p();
    }

    // replace div to p
    if (document.queryCommandValue('formatBlock') === 'div') {
      this.p();
    }
  },

  tagWhiteList: ['p', 'br', 'img', 'a', 'b', 'i', 'strike', 'u', 'h1', 'h2', 'h3', 'h4', 'pre', 'code', 'ol', 'ul', 'li', 'blockquote'],

  attrWhiteList: {
    a: ['href', 'title'],
    img: ['src', 'title', 'alt']
  },

  sanitize: function() {
    var _this = this;
    if (this.dirty) {
      // replace div to p
      while(this.article.find('div').length) {
        this.convertDivToP();
      }

      // stript not allow tags
      while(this.article.find(':not(' + this.tagWhiteList.join() + ')').length) {
        this.striptNotAllowTags();
      }

      // flatten block element
      this.article.find('> :not(blockquote)').each(function() {
        _this.flattenBlock(this);
      });
      // blockquote as a document
      this.article.find('> blockquote').find('> :not(blockquote)').each(function() {
        _this.flattenBlock(this);
      });

      // remove all attribute not in attrWhiteList
      var tags = $.map(this.attrWhiteList, function(tag) { return tag; });
      this.article.find(':not(' + tags.join() + ')').each(function() {
        $element = $(this);
        $.each(this.attributes, function(i, attr) {
          if (attr) {
            $element.removeAttr(attr.name);
          }
        });
      });

      // remove attributes not in white list for attrWhiteList
      $.each(this.attrWhiteList, function(tag, attrList) {
        _this.article.find(tag).each(function() {
          $element = $(this);
          $.each(this.attributes, function(i, attr) {
            if (attr && ($.inArray(attr.name, attrList) == -1)) {
              $element.removeAttr(attr.name);
            }
          });
        });
      });
      this.dirty = false;
    }
  },

  convertDivToP: function() {
    this.article.find('div').each(function() {
      $(this).replaceWith($('

').html($(this).html()));
    });
  },

  striptNotAllowTags: function() {
    this.article.find(':not(' + this.tagWhiteList.join() + ')').each(function() {
      $(this).replaceWith($(this).html());
    });
  },

  blockElementSelector: '> p, > h1, > h2, > h3, > h4',

  flattenBlock: function(element) {
    var _this = this;
    var hasNoBlockContent = $(element).contents().filter(function() { return this.nodeType !== 1; }).length;
    if (hasNoBlockContent) {
      // stript block
      this.flattenBlockStript(element);
    } else {
      // split block

      // stript children
      $(element).find(this.blockElementSelector).each(function() {
        _this.flattenBlockStript.call(_this, this);
      });

      // replace with last child, and set other before
      // not use replaceWith() for avoid cursor lose.
      var last = $(element).find('> :last');
      var other = $(element).find('> :not(:last)');
      $(element).html(last.html()).before(other);
    }
  },

  flattenBlockStript: function(element) {
    while($(element).find(this.blockElementSelector).length) {
      this.flattenBlockStriptExecute.call(this, element);
    }
  },

  flattenBlockStriptExecute: function(element) {
    $(element).find(this.blockElementSelector).each(function() {
      $(this).replaceWith($(this).html());
    });
  },

  stopEmptyBackspace: function(event) {
    // Stop Backspace when empty, avoid cursor flash
    if (event.keyCode === 8) {
      if (this.article.html() === '

') {
        event.preventDefault();
      }
    }
  },

  articleId: function() {
    return this.article.data('article-id');
  },

  saveArticle: function() {
    this.update({
      article: {
        title: this.extractTitle(),
        body: this.extractBody()
      }
    });
  },

  saveUrlname: function() {
    this.update($('#urlname-modal form').serializeArray(), function(data) {
      $('#topbar .urlname').text(data.urlname);
      Dialog.hide('#urlname-modal');
    });
  },

  publish: function() {
    this.update({
      article: {
        publish: true
      }
    }, function(data) {
      $('#draft-button').removeClass('button-actived');
      $('#publish-button').addClass('button-actived');
    });
  },

  draft: function() {
    this.update({
      article: {
        publish: false
      }
    }, function(data) {
      $('#publish-button').removeClass('button-actived');
      $('#draft-button').addClass('button-actived');
    });
  },

  update: function(data, success_callback, error_callback) {
    $.ajax({
      url: '/articles/' + this.articleId(),
      data: data,
      type: 'put',
      dataType: 'json'
    }).success(success_callback).error(error_callback);
  },

  extractTitle: function() {
    return this.article.find('h1').text();
  },

  extractBody: function() {
    return this.article.html();
  }
};

var editor;
$(function() {
  editor = new Editor();
});
<commit_msg>fix code with sanitize.<commit_after>Mousetrap.stopCallback = function(e, element, combo) {
  // stop for input, select, and textarea
  return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA';
};

var Editor = function() {
  this.toolbar = $('#toolbar');
  this.article = $('#editarea article');

  this.connectEvents();
  this.connectShortcuts();

  this.article.focus();
  this.initParagraph();
};

Editor.prototype = {
  events: {
    'click #save-button': 'saveArticle',
    'submit #urlname-modal form':  'saveUrlname',
    'click #draft-button': 'draft',
    'click #publish-button': 'publish',
    'keyup #editarea article': 'keyup',
    'keydown #editarea article': 'keydown',
    'mouseup #editarea article': 'detectState',
    'paste #editarea article': 'paste',
    'click #toolbar [data-command]': 'toolbarCommand'
  },

  connectEvents: function() {
    var _this = this;
    $.each(this.events, function(key, method) {
      var actions = key.split(' ');
      var event = actions.shift();
      var selector = actions.join(' ');
      $(selector).on(event, function(event) {
        _this[method].call(_this, event, this);
      });
    });
  },

  shortcuts: {
    'ctrl+b': 'bold',
    'ctrl+i': 'italic',
    'ctrl+d': 'strikeThrough',
    'ctrl+u': 'underline',
    'ctrl+l': 'createLink',
    'ctrl+shift+l': 'insertUnorderedList',
    'ctrl+shift+o': 'insertOrderedList',
    'ctrl+p': 'p',
    'ctrl+1': 'h1',
    'ctrl+2': 'h2',
    'ctrl+3': 'h3',
    'ctrl+4': 'h4',
    'ctrl+k': 'code',
    'ctrl+q': 'blockquote',
    'ctrl+s': 'saveArticle'
  },

  connectShortcuts: function() {
    var _this = this;
    $.each(this.shortcuts, function(key, method) {
      Mousetrap.bind(key, function(event) {
        event.preventDefault();
        _this[method].call(_this);
      });
    });
  },

  toolbarCommand: function(event, element) {
    event.preventDefault();
    this[$(element).data('command')].call(this);
    this.detectButton();
    this.detectBlocks();
  },

  detectState: function() {
    this.detectButton();
    this.detectBlocks();
  },

  detectButton: function() {
    var _this = this;

    _this.toolbar.find('[data-command]').each(function(index, element) {
      var command = $(element).data('command');
      if (document.queryCommandValue(command) !== 'true') {
        $(element).removeClass('actived');
      } else {
        if (command === 'bold' && /^h/.test(document.queryCommandValue('formatBlock'))) {
          $(element).removeClass('actived');
        } else {
          $(element).addClass('actived');
        }
      }
    });
  },

  detectBlocks: function() {
    var type = document.queryCommandValue('formatBlock');
    var text = this.toolbar.find('#format-block [data-command=' + type + ']').text();
    if (text === '') {
      text = this.toolbar.find('#format-block [data-command]:first').text();
    }
    this.toolbar.find('#format-block .toolbar-botton').text(text);
  },

  paste: function(event) {
    this.dirty = true;
  },

  bold: function() {
    this.exec('bold');
  },

  italic: function() {
    this.exec('italic');
  },

  strikeThrough: function() {
    this.exec('strikeThrough');
  },

  underline: function() {
    this.exec('underline');
  },

  insertOrderedList: function() {
    this.exec('insertOrderedList');
  },

  insertUnorderedList: function() {
    this.exec('insertUnorderedList');
  },

  createLink: function() {
    var url = prompt('Link url:', 'http://');
    if (url !== null && url !== '') {
      this.exec('createLink', url);
    }
  },

  p: function() {
    this.formatBlock('p');
  },

  formatHeader: function(type) {
    if (document.queryCommandValue('formatBlock') === type) {
      this.p();
    } else {
      this.formatBlock(type);
    }
  },

  h1: function() {
    this.formatHeader('h1');
  },

  h2: function() {
    this.formatHeader('h2');
  },

  h3: function() {
    this.formatHeader('h3');
  },

  h4: function() {
    this.formatHeader('h4');
  },

  code: function() {
    var selection = window.getSelection();
    var range = selection.getRangeAt(0);
    var rangeAncestor = range.commonAncestorContainer;
    var start, end, $contents;

    var $code = $(rangeAncestor).closest('code');

    if ($code.length) {
      // remove code
      if ($code.closest('pre').length) {
        // pre code
        this.splitCode($code);
        $contents = $code.contents();
        $code.closest('pre').replaceWith($contents);
        this.selectContents($contents);
      } else {
        // inline code
        $contents = $code.contents();
        $code.replaceWith($code.contents());
        this.selectContents($contents);
      }
    } else {
      // wrap code
      var isEmptyRange = (range.toString() === '');
      var isWholeBlock = (range.toString() === $(range.startContainer).closest('p, h1, h2, h3, h4').text());
      var hasBlock = (range.cloneContents().querySelector('p, h1, h2, h3, h4'));
      if (isEmptyRange || isWholeBlock || hasBlock) {
        // pre code
        start = $(range.startContainer).closest('article > *')[0];
        end = $(range.endContainer).closest('article > *')[0];
        range.setStartBefore(start);
        range.setEndAfter(end);
        $code = $('').html(range.extractContents());
        $pre = $('
').html($code);
        range.insertNode($pre[0]);
        if ($pre.next().length === 0) {
          $('

').insertAfter($pre);
        }
      } else {
        // inline code
        $code = $('').html(range.extractContents());
        range.insertNode($code[0]);
      }
      selection.selectAllChildren($code[0]);
      this.striptCode($code);
    }
  },

  striptCode: function(code) {
    code.find('p, h1, h2, h3, h4').each(function() {
      $(this).replaceWith($(this).text() + "\n");
    }).children().each(function() {
      $(this).replaceWith($(this).text());
    });
  },

  splitCode: function(code) {
    code.html($.map(code.text().split("\n"), function(line) {
      if (line !== '') {
        return $('
').text(line);
      }
    }));
  },

  selectContents: function(contents) {
    var selection = window.getSelection();
    var range = selection.getRangeAt(0);
    var start = contents.first()[0];
    var end = contents.last()[0];
    range.setStart(start, 0);
    range.setEnd(end, end.childNodes.length || end.length); // text node don't have childNodes
    console.log(range);
    selection.removeAllRanges();
    selection.addRange(range);
  },

  blockquote: function() {
    var selection = window.getSelection();
    var range = selection.getRangeAt(0);
    var rangeAncestor = range.commonAncestorContainer;
    var start, end;

    var $blockquote = $(rangeAncestor).closest('blockquote');
    if ($blockquote.length) {
      // remmove blockquote
      var $contents = $blockquote.contents();
      $blockquote.replaceWith($contents);
      this.selectContents($contents);
    } else {
      // wrap blockquote
      start = $(range.startContainer).closest('article > *')[0];
      end = $(range.endContainer).closest('article > *')[0];
      range.setStartBefore(start);
      range.setEndAfter(end);
      $blockquote = $('
');
      $blockquote.html(range.extractContents()).find('blockquote').each(function() {
        $(this).replaceWith($(this).html());
      });
      range.insertNode($blockquote[0]);
      selection.selectAllChildren($blockquote[0]);
      if ($blockquote.next().length === 0) {
        $('

').insertAfter($blockquote);
      }
    }
  },

  formatBlock: function(type) {
    this.exec('formatBlock', type);
  },

  exec: function(command, arg) {
    document.execCommand(command, false, arg);
  },

  keyup: function() {
    this.initParagraph();
    this.detectState();
    this.sanitize();
  },

  keydown: function(event) {
    this.stopEmptyBackspace(event);
  },

  initParagraph: function() {
    // chrome is empty and firefox is

    if (this.article.html() === '' || this.article.html() === '
') {
      this.p();
    }

    // replace div to p
    if (document.queryCommandValue('formatBlock') === 'div') {
      this.p();
    }
  },

  tagWhiteList: ['p', 'br', 'img', 'a', 'b', 'i', 'strike', 'u', 'h1', 'h2', 'h3', 'h4', 'pre', 'code', 'ol', 'ul', 'li', 'blockquote'],

  attrWhiteList: {
    a: ['href', 'title'],
    img: ['src', 'title', 'alt']
  },

  sanitize: function() {
    var _this = this;
    if (this.dirty) {
      // replace div to p
      while(this.article.find('div').length) {
        this.convertDivToP();
      }

      // stript not allow tags
      while(this.article.find(':not(' + this.tagWhiteList.join() + ')').length) {
        this.striptNotAllowTags();
      }

      // flatten block element
      this.article.find('> :not(blockquote, pre)').each(function() {
        _this.flattenBlock(this);
      });
      // blockquote as a document
      this.article.find('> blockquote').find('> :not(blockquote, pre)').each(function() {
        _this.flattenBlock(this);
      });

      // remove all attribute not in attrWhiteList
      var tags = $.map(this.attrWhiteList, function(tag) { return tag; });
      this.article.find(':not(' + tags.join() + ')').each(function() {
        $element = $(this);
        $.each(this.attributes, function(i, attr) {
          if (attr) {
            $element.removeAttr(attr.name);
          }
        });
      });

      // remove attributes not in white list for attrWhiteList
      $.each(this.attrWhiteList, function(tag, attrList) {
        _this.article.find(tag).each(function() {
          $element = $(this);
          $.each(this.attributes, function(i, attr) {
            if (attr && ($.inArray(attr.name, attrList) == -1)) {
              $element.removeAttr(attr.name);
            }
          });
        });
      });
      this.dirty = false;
    }
  },

  convertDivToP: function() {
    this.article.find('div').each(function() {
      // don't use replaceWith, it flash cursor
      $(this).before($(this).html().wrap('
')).remove();
    });
  },

  striptNotAllowTags: function() {
    this.article.find(':not(' + this.tagWhiteList.join() + ')').each(function() {
      // don't use replaceWith, it flash cursor
      $(this).before($(this).html()).remove();
    });
  },

  blockElementSelector: '> p, > h1, > h2, > h3, > h4',

  flattenBlock: function(element) {
    var _this = this;
    var hasNoBlockContent = $(element).contents().filter(function() { return this.nodeType !== 1; }).length;
    if (hasNoBlockContent) {
      // stript block
      this.flattenBlockStript(element);
    } else {
      // split block

      // stript children
      $(element).find(this.blockElementSelector).each(function() {
        _this.flattenBlockStript.call(_this, this);
      });

      // don't replaceWith() for avoid cursor lose.
      $(element).before($(element).html()).remove();
    }
  },

  flattenBlockStript: function(element) {
    while($(element).find(this.blockElementSelector).length) {
      this.flattenBlockStriptExecute.call(this, element);
    }
  },

  flattenBlockStriptExecute: function(element) {
    $(element).find(this.blockElementSelector).each(function() {
      $(this).replaceWith($(this).html());
    });
  },

  stopEmptyBackspace: function(event) {
    // Stop Backspace when empty, avoid cursor flash
    if (event.keyCode === 8) {
      if (this.article.html() === '

') {
        event.preventDefault();
      }
    }
  },

  articleId: function() {
    return this.article.data('article-id');
  },

  saveArticle: function() {
    this.update({
      article: {
        title: this.extractTitle(),
        body: this.extractBody()
      }
    });
  },

  saveUrlname: function() {
    this.update($('#urlname-modal form').serializeArray(), function(data) {
      $('#topbar .urlname').text(data.urlname);
      Dialog.hide('#urlname-modal');
    });
  },

  publish: function() {
    this.update({
      article: {
        publish: true
      }
    }, function(data) {
      $('#draft-button').removeClass('button-actived');
      $('#publish-button').addClass('button-actived');
    });
  },

  draft: function() {
    this.update({
      article: {
        publish: false
      }
    }, function(data) {
      $('#publish-button').removeClass('button-actived');
      $('#draft-button').addClass('button-actived');
    });
  },

  update: function(data, success_callback, error_callback) {
    $.ajax({
      url: '/articles/' + this.articleId(),
      data: data,
      type: 'put',
      dataType: 'json'
    }).success(success_callback).error(error_callback);
  },

  extractTitle: function() {
    return this.article.find('h1').text();
  },

  extractBody: function() {
    return this.article.html();
  }
};

var editor;
$(function() {
  editor = new Editor();
});
<|endoftext|>