<commit_before>
require('core/string');
require('core/array');
var Buffer = require('helma/buffer').Buffer;

exports.fileoverview_filter = function(docs) {
    var buffer = new Buffer('
');
    if (docs.fileoverview) {
        buffer.write(docs.fileoverview.getTag("fileoverview"));
    }
    var topItems = {children: {}};
    for (var i = 0; i < docs.length; i++) {
        var doc = docs[i];
        var name = doc.name;
        // pragmatical class detection
        doc.isClass = doc.isClass || docs[i + 1] && docs[i + 1].name.startsWith(name + ".");
        var path = name.split(".");
        // FIXME: __iterator__ property breaks TOC enumeration
        if (path.peek() == "__iterator__") continue;
        var id = doc.id = path.join("_");
        var parent = topItems;
        for (var p = 0, l = path.length - 1; p < l; p++) {
            var elem = path[p];
            if (elem == "prototype" || elem == "instance") {
                elem = doc.isFunction ? "Instance Methods" : "Instance Properties";
            }
            parent = getGroup(parent, elem, parent == topItems);
        }
        // try to detect static members
        if (parent.isClass) {
            var staticName = doc.isFunction ? "Static Methods" : "Static Properties";
            parent = getGroup(parent, staticName, false);
        } else if (parent == topItems && !doc.isClass) {
            var groupName = doc.isFunction ? "Functions" : "Properties";
            parent = getGroup(parent, groupName, false);
        }
        parent.children[path[p]] = doc.node = {
            item: doc,
            children: {},
            isClass: doc.isClass
        };
    }
    buffer.writeln('
')
            .writeln('
Overview
');
    function render(node, name, prefix) {
        if (name) {
            buffer.write('
');
            if (node.item) {
                var item = node.item;
                if (item.isFunction && !item.isClass) name += "()";
                if (node.isClass) name = "Class " + name;
                buffer.write('', name, '');
            } else {
                node.isClass ?
                    buffer.write("Class ", name) :
                    buffer.write("", name, "");
            }
        }
        var hasList = false;
        for (var child in node.children) {
            if (!hasList) {
                buffer.writeln('
');
                hasList = true;
            }
            render(node.children[child], child);
        }
        if (hasList) {
            buffer.writeln('
');
        }
        if (name) {
            buffer.writeln('
');
        }
    }
    render(topItems, null, "");
    buffer.writeln('
Detail
');
    return buffer;
};

exports.renderName_filter = function(doc) {
    var name = doc.name;
    var id = name.replace(/\./g, "_");
    var buffer = new Buffer('');
    var dot1 = name.indexOf('.');
    var dot2 = name.lastIndexOf('.');
    if (dot1 != dot2) {
        buffer.write(
            name.substring(0, dot1),
            '',
            name.substring(dot1, dot2 + 1),
            '',
            name.substring(dot2 + 1)
        );
    } else {
        buffer.write(name);
    }
    var processedParams = [];
    if (doc.isFunction) {
        var params = doc.getTags("param");
        buffer.write('(');
        for (var i = 0; i < params.length; i++) {
            var words = params[i].split(" ");
            var type = words[0].match(/^{(\S+)}$/);
            type = type && type[1];
            if (i > 0) buffer.write(", ");
            var pname = type ? words[1] : words[0];
            buffer.write(pname);
            // add to processed parameter list
            var desc = words.slice(type ? 2 : 1).join(" ");
            processedParams.push({type: type, name: pname, desc: desc});
        }
        buffer.write(')');
        // cache processed parameters
        doc.processedParams = processedParams;
    }
    // doc header anchor link
    buffer.write('');
    buffer.write(' ¶ ');
    return buffer;
};

exports.renderDoc_filter = function(doc) {
    var buffer = new Buffer("
", doc.getTag("desc") || "No description available.", "
");
    if (doc.processedParams && doc.processedParams.length) {
        buffer.writeln('
Parameters
');
        buffer.writeln('
');
        for each (var param in doc.processedParams) {
            buffer.write('
');
            var name = ''+param.name+'';
            buffer.write([param.type, name, param.desc].join('	'));
            buffer.writeln('
');
        }
        buffer.writeln('
');
    }
    var returns = doc.getTag("returns");
    var type = doc.getTag("type");
    if (returns || type) {
        buffer.writeln('
Returns
');
        if (!type) {
            type = returns.match(/^{(\S+)}/) || "";
            if (type) {
                returns = returns.substring(type[0].length);
                type = type[1];
            }
        }
        buffer.writeln('
');
        buffer.writeln('
', type, '	', returns, '
');
        buffer.writeln('
');
    }
    var see = doc.getTags("see");
    if (see.length) {
        buffer.writeln('
See
');
        for each (var link in see) {
            if (link.isUrl()) {
                link = '' + link + '';
            } else {
                // apply some sanity checks to local targets like removing hashes and parantheses
                link = link.replace(/^#/, '');
                var id = link.replace(/\./g, '_').replace(/[\(\)]/g, '');
                link = '' + link + '';
            }
            buffer.writeln('
', link, '
');
        }
    }
    return buffer;
};

function getGroup(parent, groupName, isClass) {
    var group = parent.children[groupName];
    if (!group) {
        group = parent.children[groupName] = {
            children: {},
            isClass: isClass
        };
    }
    return group;
}
<commit_msg>Add support for @throws, @example, @since tags<commit_after>
require('core/string');
require('core/array');
var Buffer = require('helma/buffer').Buffer;

exports.fileoverview_filter = function(docs) {
    var buffer = new Buffer();
    if (docs.fileoverview) {
        buffer.write('
');
        buffer.write(docs.fileoverview.getTag("fileoverview"));
        buffer.writeln('
');
        renderStandardTags(docs.fileoverview, buffer);
    }
    var topItems = {children: {}};
    for (var i = 0; i < docs.length; i++) {
        var doc = docs[i];
        var name = doc.name;
        // pragmatical class detection
        doc.isClass = doc.isClass || docs[i + 1] && docs[i + 1].name.startsWith(name + ".");
        var path = name.split(".");
        // FIXME: __iterator__ property breaks TOC enumeration
        if (path.peek() == "__iterator__") continue;
        var id = doc.id = path.join("_");
        var parent = topItems;
        for (var p = 0, l = path.length - 1; p < l; p++) {
            var elem = path[p];
            if (elem == "prototype" || elem == "instance") {
                elem = doc.isFunction ? "Instance Methods" : "Instance Properties";
            }
            parent = getGroup(parent, elem, parent == topItems);
        }
        // try to detect static members
        if (parent.isClass) {
            var staticName = doc.isFunction ? "Static Methods" : "Static Properties";
            parent = getGroup(parent, staticName, false);
        } else if (parent == topItems && !doc.isClass) {
            var groupName = doc.isFunction ? "Functions" : "Properties";
            parent = getGroup(parent, groupName, false);
        }
        parent.children[path[p]] = doc.node = {
            item: doc,
            children: {},
            isClass: doc.isClass
        };
    }
    buffer.writeln('
Overview
');
    function render(node, name, prefix) {
        if (name) {
            buffer.write('
');
            if (node.item) {
                var item = node.item;
                if (item.isFunction && !item.isClass) name += "()";
                if (node.isClass) name = "Class " + name;
                buffer.write('', name, '');
            } else {
                node.isClass ?
                    buffer.write("Class ", name) :
                    buffer.write("", name, "");
            }
        }
        var hasList = false;
        for (var child in node.children) {
            if (!hasList) {
                buffer.writeln('
');
                hasList = true;
            }
            render(node.children[child], child);
        }
        if (hasList) {
            buffer.writeln('
');
        }
        if (name) {
            buffer.writeln('
');
        }
    }
    render(topItems, null, "");
    buffer.writeln('
Detail
');
    return buffer;
};

exports.renderName_filter = function(doc) {
    var name = doc.name;
    var id = name.replace(/\./g, "_");
    var buffer = new Buffer('');
    var dot1 = name.indexOf('.');
    var dot2 = name.lastIndexOf('.');
    if (dot1 != dot2) {
        buffer.write(
            name.substring(0, dot1),
            '',
            name.substring(dot1, dot2 + 1),
            '',
            name.substring(dot2 + 1)
        );
    } else {
        buffer.write(name);
    }
    var processedParams = [];
    if (doc.isFunction) {
        var params = doc.getTags("param");
        buffer.write('(');
        for (var i = 0; i < params.length; i++) {
            var words = params[i].split(" ");
            var type = words[0].match(/^{(\S+)}$/);
            type = type && type[1];
            if (i > 0) buffer.write(", ");
            var pname = type ? words[1] : words[0];
            buffer.write(pname);
            // add to processed parameter list
            var desc = words.slice(type ? 2 : 1).join(" ");
            processedParams.push({type: type, name: pname, desc: desc});
        }
        buffer.write(')');
        // cache processed parameters
        doc.processedParams = processedParams;
    }
    // doc header anchor link
    buffer.write('');
    buffer.write(' ¶ ');
    return buffer;
};

exports.renderDoc_filter = function(doc) {
    var buffer = new Buffer("
", doc.getTag("desc") || "No description available.", "
");
    renderStandardTags(doc, buffer);
    if (doc.processedParams && doc.processedParams.length) {
        buffer.writeln('
Parameters
');
        buffer.writeln('
');
        for each (var param in doc.processedParams) {
            buffer.write('
');
            var name = ''+param.name+'';
            buffer.write([param.type, name, param.desc].join('	'));
            buffer.writeln('
');
        }
        buffer.writeln('
');
    }
    var returns = doc.getTag("returns") || doc.getTag("return");
    var type = doc.getTag("type");
    if (returns || type) {
        buffer.writeln('
Returns
');
        if (!type) {
            type = returns.match(/^{(\S+)}/) || "";
            if (type) {
                returns = returns.substring(type[0].length);
                type = type[1];
            }
        }
        buffer.writeln('
');
        buffer.writeln('
', type, '	', returns, '
');
        buffer.writeln('
');
    }
    var throws = doc.getTags("throws");
    if (throws.length) {
        buffer.writeln('
Throws
');
        for each (var error in throws) {
            buffer.writeln('
', error, '
');
        }
    }
    var see = doc.getTags("see");
    if (see.length) {
        buffer.writeln('
See
');
        for each (var link in see) {
            if (link.isUrl()) {
                link = '' + link + '';
            } else {
                // apply some sanity checks to local targets like removing hashes and parantheses
                link = link.replace(/^#/, '');
                var id = link.replace(/\./g, '_').replace(/[\(\)]/g, '');
                link = '' + link + '';
            }
            buffer.writeln('
', link, '
');
        }
    }
    return buffer;
};

function renderStandardTags(doc, buffer) {
    var example = doc.getTag("example");
    if (example) {
        buffer.writeln('
Example
');
        buffer.writeln('
', example, '
');
    }
    var since = doc.getTag("since") ||  doc.getTag("version");
    if (since) {
        buffer.writeln('
Since
');
        buffer.writeln('
', since, '
');
    }
}

function getGroup(parent, groupName, isClass) {
    var group = parent.children[groupName];
    if (!group) {
        group = parent.children[groupName] = {
            children: {},
            isClass: isClass
        };
    }
    return group;
}
<|endoftext|>


<commit_before>;(function() {
	var url = location.protocol+'\/\/'+location.hostname+":{$port}";
	window._start_ristretto = function(Faye){
		if(!Faye) {
			console.warn('You shall turn Ristretto on.');
		} else {
			var swap_links = function(link, first_time) {
				var char = (link.href.indexOf('?')>-1) ? '&' : '?';
				var new_link = link.cloneNode();
				new_link.href += (first_time === true) ? (char+(new Date()).getTime()) : char;
				var parent = link.parentNode;
				new_link.onload = function(){
					parent.removeChild(link);
				};
				parent.appendChild(new_link);
		};
			var reload_stylesheets = function(first_time) {
				var links = document.getElementsByTagName("link");
				var link;
				var replacing = [];
				for(var i = 0; i < links.length; i++) {
					link = links[i];
					char = '?';
					if(link.rel === "stylesheet") {
						replacing.push(link);
					}
				}
				for(i=0; i < replacing.length; i++) {
					swap_links(replacing[i], first_time);
				}
			};
			var connect_ristretto = function() {
				var client = new Faye.Client(url+'\/faye', {
					retry: 1,
					timeout: 3
				});
				client.disable('autodisconnect');
				client.subscribe('\/reload', function(message) {
					switch(message.type) {
						case 'stylesheets':
							reload_stylesheets();
							break;
						case 'page':
							window.location.reload();
							break;
					}
				});
				reload_stylesheets(true);
			};
			connect_ristretto();
		}
	};
	(function(){var s=document.createElement("script");s.setAttribute("src", "http://"+location.hostname+":{$port}/faye.js");s.onload=function(){window._start_ristretto(Faye)};document.getElementsByTagName("body")[0].appendChild(s);void(s);})();
})();<commit_msg>Solved client-side script IE compatibility issues<commit_after>;(function() {
	var url = location.protocol+'\/\/'+location.hostname+":{$port}";
	window._start_ristretto = function(Faye){
		if(!Faye) {
			if(typeof console !== 'undefined') {
				console.warn('You shall turn Ristretto on.');
			}
		} else {
			var swap_links = function(link, first_time) {
				var char = (link.href.indexOf('?')>-1) ? '&' : '?';
				var new_link = link.cloneNode();
				new_link.href += (first_time === true) ? (char+(new Date()).getTime()) : char;
				var parent = link.parentNode;
				new_link.onload = function(){
					parent.removeChild(link);
				};
				parent.appendChild(new_link);
		};
			var reload_stylesheets = function(first_time) {
				var links = document.getElementsByTagName("link");
				var link;
				var replacing = [];
				for(var i = 0; i < links.length; i++) {
					link = links[i];
					char = '?';
					if(link.rel === "stylesheet") {
						replacing.push(link);
					}
				}
				for(i=0; i < replacing.length; i++) {
					swap_links(replacing[i], first_time);
				}
			};
			var connect_ristretto = function() {
				var client = new Faye.Client(url+'\/faye', {
					retry: 1,
					timeout: 3
				});
				client.disable('autodisconnect');
				client.subscribe('\/reload', function(message) {
					switch(message.type) {
						case 'stylesheets':
							reload_stylesheets();
							break;
						case 'page':
							window.location.reload();
							break;
					}
				});
				reload_stylesheets(true);
			};
			connect_ristretto();
		}
	};
	(function(){
		function scriptTag(src, callback) {
			var s = document.createElement('script');
			s.type = 'text/' + (src.type || 'javascript');
			s.src = src.src || src;
			s.async = false;
			s.onreadystatechange = s.onload = function () {
				var state = s.readyState;
				if (!callback.done && (!state || /loaded|complete/.test(state))) {
					callback.done = true;
					callback();
				}
			};
			(document.body || head).appendChild(s);
		};
		var script_loaded = function() {
			window._start_ristretto(Faye);
		};
		scriptTag("http://"+location.hostname+":{$port}/faye.js", script_loaded);
	})();
})();
<|endoftext|>


<commit_before>window.c.models = (function(m) {
    var contributionDetail = m.postgrest.model('contribution_details'),
        projectDetail = m.postgrest.model('project_details'),
        userDetail = m.postgrest.model('user_details'),
        user = m.postgrest.model('users'),
        rewardDetail = m.postgrest.model('reward_details'),
        projectReminder = m.postgrest.model('project_reminders'),
        contributions = m.postgrest.model('contributions'),
        teamTotal = m.postgrest.model('team_totals'),
        projectContribution = m.postgrest.model('project_contributions'),
        projectPostDetail = m.postgrest.model('project_posts_details'),
        projectContributionsPerDay = m.postgrest.model('project_contributions_per_day'),
        projectContributionsPerLocation = m.postgrest.model('project_contributions_per_location'),
        projectContributionsPerRef = m.postgrest.model('project_contributions_per_ref'),
        project = m.postgrest.model('projects'),
        category = m.postgrest.model('categories'),
        categoryFollower = m.postgrest.model('category_followers'),
        teamMember = m.postgrest.model('team_members'),
        notification = m.postgrest.model('notifications'),
        statistic = m.postgrest.model('statistics');

    teamMember.pageSize(40);
    rewardDetail.pageSize(false);
    project.pageSize(30);
    category.pageSize(50);

    return {
        contributionDetail: contributionDetail,
        projectDetail: projectDetail,
        userDetail: userDetail,
        user: user,
        rewardDetail: rewardDetail,
        contributions: contributions,
        teamTotal: teamTotal,
        teamMember: teamMember,
        project: project,
        category: category,
        categoryFollower: categoryFollower,
        projectContributionsPerDay: projectContributionsPerDay,
        projectContributionsPerLocation: projectContributionsPerLocation,
        projectContributionsPerRef: projectContributionsPerRef,
        projectContribution: projectContribution,
        projectPostDetail: projectPostDetail,
        projectReminder: projectReminder,
        notification: notification,
        statistic: statistic
    };
}(window.m));
<commit_msg>Adds project_search model<commit_after>window.c.models = (function(m) {
    var contributionDetail = m.postgrest.model('contribution_details'),
        projectDetail = m.postgrest.model('project_details'),
        userDetail = m.postgrest.model('user_details'),
        user = m.postgrest.model('users'),
        rewardDetail = m.postgrest.model('reward_details'),
        projectReminder = m.postgrest.model('project_reminders'),
        contributions = m.postgrest.model('contributions'),
        teamTotal = m.postgrest.model('team_totals'),
        projectContribution = m.postgrest.model('project_contributions'),
        projectPostDetail = m.postgrest.model('project_posts_details'),
        projectContributionsPerDay = m.postgrest.model('project_contributions_per_day'),
        projectContributionsPerLocation = m.postgrest.model('project_contributions_per_location'),
        projectContributionsPerRef = m.postgrest.model('project_contributions_per_ref'),
        project = m.postgrest.model('projects'),
        projectSearch = m.postgrest.model('/rpc/project_search'),
        category = m.postgrest.model('categories'),
        categoryFollower = m.postgrest.model('category_followers'),
        teamMember = m.postgrest.model('team_members'),
        notification = m.postgrest.model('notifications'),
        statistic = m.postgrest.model('statistics');

    teamMember.pageSize(40);
    rewardDetail.pageSize(false);
    project.pageSize(30);
    category.pageSize(50);

    return {
        contributionDetail: contributionDetail,
        projectDetail: projectDetail,
        userDetail: userDetail,
        user: user,
        rewardDetail: rewardDetail,
        contributions: contributions,
        teamTotal: teamTotal,
        teamMember: teamMember,
        project: project,
        projectSearch: projectSearch,
        category: category,
        categoryFollower: categoryFollower,
        projectContributionsPerDay: projectContributionsPerDay,
        projectContributionsPerLocation: projectContributionsPerLocation,
        projectContributionsPerRef: projectContributionsPerRef,
        projectContribution: projectContribution,
        projectPostDetail: projectPostDetail,
        projectReminder: projectReminder,
        notification: notification,
        statistic: statistic
    };
}(window.m));
<|endoftext|>


<commit_before>/*global inheritVars */

inheritVars.model.defaultOptions.populate = true;

var oldModelChange = inheritVars.model.change;
inheritVars.model.change = function() {
  oldModelChange.apply(this, arguments);
  // TODO : What can we do to remove this duplication?
  var modelOptions = this.model && this._objectOptionsByCid[this.model.cid];
  if (modelOptions && modelOptions.populate) {
    this.populate(this.model.attributes, modelOptions.populate === true ? {} : modelOptions.populate);
  }
};
inheritVars.model.defaultOptions.populate = true;

_.extend(Thorax.View.prototype, {
  //serializes a form present in the view, returning the serialized data
  //as an object
  //pass {set:false} to not update this.model if present
  //can pass options, callback or event in any order
  serialize: function() {
    var callback, options, event;
    //ignore undefined arguments in case event was null
    for (var i = 0; i < arguments.length; ++i) {
      if (typeof arguments[i] === 'function') {
        callback = arguments[i];
      } else if (typeof arguments[i] === 'object') {
        if ('stopPropagation' in arguments[i] && 'preventDefault' in arguments[i]) {
          event = arguments[i];
        } else {
          options = arguments[i];
        }
      }
    }

    if (event && !this._preventDuplicateSubmission(event)) {
      return;
    }

    options = _.extend({
      set: true,
      validate: true,
      children: true,
      silent: true
    }, options || {});

    var attributes = options.attributes || {};

    //callback has context of element
    var view = this;
    var errors = [];
    eachNamedInput.call(this, options, function() {
      var value = view._getInputValue(this, options, errors);
      if (typeof value !== 'undefined') {
        objectAndKeyFromAttributesAndName.call(this, attributes, this.name, {mode: 'serialize'}, function(object, key) {
          if (!object[key]) {
            object[key] = value;
          } else if (_.isArray(object[key])) {
            object[key].push(value);
          } else {
            object[key] = [object[key], value];
          }
        });
      }
    });

    this.trigger('serialize', attributes, options);

    if (options.validate) {
      var validateInputErrors = this.validateInput(attributes);
      if (validateInputErrors && validateInputErrors.length) {
        errors = errors.concat(validateInputErrors);
      }
      this.trigger('validate', attributes, errors, options);
      if (errors.length) {
        this.trigger('error', errors);
        return;
      }
    }

    if (options.set && this.model) {
      if (!this.model.set(attributes, {silent: options.silent})) {
        return false;
      }
    }

    callback && callback.call(this, attributes, _.bind(resetSubmitState, this));
    return attributes;
  },

  _preventDuplicateSubmission: function(event, callback) {
    event.preventDefault();

    var form = $(event.target);
    if ((event.target.tagName || '').toLowerCase() !== 'form') {
      // Handle non-submit events by gating on the form
      form = $(event.target).closest('form');
    }

    if (!form.attr('data-submit-wait')) {
      form.attr('data-submit-wait', 'true');
      if (callback) {
        callback.call(this, event);
      }
      return true;
    } else {
      return false;
    }
  },

  //populate a form from the passed attributes or this.model if present
  populate: function(attributes, options) {
    options = _.extend({
      children: true
    }, options || {});
    var value, attributes = attributes || this._getContext();
    //callback has context of element
    eachNamedInput.call(this, options, function() {
      objectAndKeyFromAttributesAndName.call(this, attributes, this.name, {mode: 'populate'}, function(object, key) {
        if (object && typeof (value = object[key]) !== 'undefined') {
          //will only execute if we have a name that matches the structure in attributes
          if (this.type === 'checkbox' && _.isBoolean(value)) {
            this.checked = value;
          } else if (this.type === 'checkbox' || this.type === 'radio') {
            this.checked = value == this.value;
          } else {
            this.value = value;
          }
        }
      });
    });

    this.trigger('populate', attributes);
  },

  //perform form validation, implemented by child class
  validateInput: function(/* attributes, options, errors */) {},

  _getInputValue: function(input /* , options, errors */) {
    if (input.type === 'checkbox' || input.type === 'radio') {
      if (input.checked) {
        return input.value;
      }
    } else if (input.multiple === true) {
      var values = [];
      $('option', input).each(function() {
        if (this.selected) {
          values.push(this.value);
        }
      });
      return values;
    } else {
      return input.value;
    }
  }
});

Thorax.View.on({
  error: function() {
    resetSubmitState.call(this);

    // If we errored with a model we want to reset the content but leave the UI
    // intact. If the user updates the data and serializes any overwritten data
    // will be restored.
    if (this.model && this.model.previousAttributes) {
      this.model.set(this.model.previousAttributes(), {
        silent: true
      });
    }
  },
  deactivated: function() {
    resetSubmitState.call(this);
  }
});

function eachNamedInput(options, iterator, context) {
  var i = 0,
      self = this;

  this.$('select,input,textarea', options.root || this.el).each(function() {
    if (!options.children) {
      if (self !== $(this).view({helper: false})) {
        return;
      }
    }
    if (this.type !== 'button' && this.type !== 'cancel' && this.type !== 'submit' && this.name && this.name !== '') {
      iterator.call(context || this, i, this);
      ++i;
    }
  });
}

//calls a callback with the correct object fragment and key from a compound name
function objectAndKeyFromAttributesAndName(attributes, name, options, callback) {
  var key,
      object = attributes,
      keys = name.split('['),
      mode = options.mode;

  for (var i = 0; i < keys.length - 1; ++i) {
    key = keys[i].replace(']', '');
    if (!object[key]) {
      if (mode === 'serialize') {
        object[key] = {};
      } else {
        return callback.call(this, false, key);
      }
    }
    object = object[key];
  }
  key = keys[keys.length - 1].replace(']', '');
  callback.call(this, object, key);
}

function resetSubmitState() {
  this.$('form').removeAttr('data-submit-wait');
}
<commit_msg>Formatting<commit_after>/*global inheritVars */

inheritVars.model.defaultOptions.populate = true;

var oldModelChange = inheritVars.model.change;
inheritVars.model.change = function() {
  oldModelChange.apply(this, arguments);
  // TODO : What can we do to remove this duplication?
  var modelOptions = this.model && this._objectOptionsByCid[this.model.cid];
  if (modelOptions && modelOptions.populate) {
    this.populate(this.model.attributes, modelOptions.populate === true ? {} : modelOptions.populate);
  }
};
inheritVars.model.defaultOptions.populate = true;

_.extend(Thorax.View.prototype, {
  //serializes a form present in the view, returning the serialized data
  //as an object
  //pass {set:false} to not update this.model if present
  //can pass options, callback or event in any order
  serialize: function() {
    var callback, options, event;
    //ignore undefined arguments in case event was null
    for (var i = 0; i < arguments.length; ++i) {
      if (typeof arguments[i] === 'function') {
        callback = arguments[i];
      } else if (typeof arguments[i] === 'object') {
        if ('stopPropagation' in arguments[i] && 'preventDefault' in arguments[i]) {
          event = arguments[i];
        } else {
          options = arguments[i];
        }
      }
    }

    if (event && !this._preventDuplicateSubmission(event)) {
      return;
    }

    options = _.extend({
      set: true,
      validate: true,
      children: true,
      silent: true
    }, options || {});

    var attributes = options.attributes || {};

    //callback has context of element
    var view = this;
    var errors = [];
    eachNamedInput.call(this, options, function() {
      var value = view._getInputValue(this, options, errors);
      if (typeof value !== 'undefined') {
        objectAndKeyFromAttributesAndName.call(this, attributes, this.name, {mode: 'serialize'}, function(object, key) {
          if (!object[key]) {
            object[key] = value;
          } else if (_.isArray(object[key])) {
            object[key].push(value);
          } else {
            object[key] = [object[key], value];
          }
        });
      }
    });

    this.trigger('serialize', attributes, options);

    if (options.validate) {
      var validateInputErrors = this.validateInput(attributes);
      if (validateInputErrors && validateInputErrors.length) {
        errors = errors.concat(validateInputErrors);
      }
      this.trigger('validate', attributes, errors, options);
      if (errors.length) {
        this.trigger('error', errors);
        return;
      }
    }

    if (options.set && this.model) {
      if (!this.model.set(attributes, {silent: options.silent})) {
        return false;
      }
    }

    callback && callback.call(this, attributes, _.bind(resetSubmitState, this));
    return attributes;
  },

  _preventDuplicateSubmission: function(event, callback) {
    event.preventDefault();

    var form = $(event.target);
    if ((event.target.tagName || '').toLowerCase() !== 'form') {
      // Handle non-submit events by gating on the form
      form = $(event.target).closest('form');
    }

    if (!form.attr('data-submit-wait')) {
      form.attr('data-submit-wait', 'true');
      if (callback) {
        callback.call(this, event);
      }
      return true;
    } else {
      return false;
    }
  },

  //populate a form from the passed attributes or this.model if present
  populate: function(attributes, options) {
    options = _.extend({
      children: true
    }, options || {});

    var value,
        attributes = attributes || this._getContext();

    //callback has context of element
    eachNamedInput.call(this, options, function() {
      objectAndKeyFromAttributesAndName.call(this, attributes, this.name, {mode: 'populate'}, function(object, key) {
        if (object && typeof (value = object[key]) !== 'undefined') {
          //will only execute if we have a name that matches the structure in attributes
          if (this.type === 'checkbox' && _.isBoolean(value)) {
            this.checked = value;
          } else if (this.type === 'checkbox' || this.type === 'radio') {
            this.checked = value == this.value;
          } else {
            this.value = value;
          }
        }
      });
    });

    this.trigger('populate', attributes);
  },

  //perform form validation, implemented by child class
  validateInput: function(/* attributes, options, errors */) {},

  _getInputValue: function(input /* , options, errors */) {
    if (input.type === 'checkbox' || input.type === 'radio') {
      if (input.checked) {
        return input.value;
      }
    } else if (input.multiple === true) {
      var values = [];
      $('option', input).each(function() {
        if (this.selected) {
          values.push(this.value);
        }
      });
      return values;
    } else {
      return input.value;
    }
  }
});

Thorax.View.on({
  error: function() {
    resetSubmitState.call(this);

    // If we errored with a model we want to reset the content but leave the UI
    // intact. If the user updates the data and serializes any overwritten data
    // will be restored.
    if (this.model && this.model.previousAttributes) {
      this.model.set(this.model.previousAttributes(), {
        silent: true
      });
    }
  },
  deactivated: function() {
    resetSubmitState.call(this);
  }
});

function eachNamedInput(options, iterator, context) {
  var i = 0,
      self = this;

  this.$('select,input,textarea', options.root || this.el).each(function() {
    if (!options.children) {
      if (self !== $(this).view({helper: false})) {
        return;
      }
    }
    if (this.type !== 'button' && this.type !== 'cancel' && this.type !== 'submit' && this.name && this.name !== '') {
      iterator.call(context || this, i, this);
      ++i;
    }
  });
}

//calls a callback with the correct object fragment and key from a compound name
function objectAndKeyFromAttributesAndName(attributes, name, options, callback) {
  var key,
      object = attributes,
      keys = name.split('['),
      mode = options.mode;

  for (var i = 0; i < keys.length - 1; ++i) {
    key = keys[i].replace(']', '');
    if (!object[key]) {
      if (mode === 'serialize') {
        object[key] = {};
      } else {
        return callback.call(this, false, key);
      }
    }
    object = object[key];
  }
  key = keys[keys.length - 1].replace(']', '');
  callback.call(this, object, key);
}

function resetSubmitState() {
  this.$('form').removeAttr('data-submit-wait');
}
<|endoftext|>


<commit_before>/**
 * MakeDrive is a single/shared Filer filesystem instance with
 * manual- and auto-sync'ing features. A client first gets the
 * filesystem instance like so:
 *
 * var fs = MakeDrive.fs();
 *
 * Multiple calls to MakeDrive.fs() will return the same instance.
 *
 * A number of configuration options can be passed to the fs() function.
 * These include:
 *
 * - manual=true - by default the filesystem syncs automatically in
 * the background. This disables it.
 *
 * - memory=<Boolean> - by default we use a persistent store (indexeddb
 * or websql). Using memory=true overrides and uses a temporary ram disk.
 *
 * - provider=<Object> - a Filer data provider to use instead of the
 * default provider normally used. The provider given should already
 * be instantiated (i.e., don't pass a constructor function).
 *
 * - forceCreate=<Boolean> - by default we return the same fs instance with
 * every call to MakeDrive.fs(). In some cases it is necessary to have
 * multiple instances.  Using forceCreate=true does this.
 *
 * - interval=<Number> - by default, the filesystem syncs every minute if
 * auto syncing is turned on otherwise the interval between syncs can be
 * specified in ms.
 *
 * Various bits of Filer are available on MakeDrive, including:
 *
 * - MakeDrive.Buffer
 * - MakeDrive.Path
 * - MakeDrive.Errors
 *
 * The filesystem instance returned by MakeDrive.fs() also includes
 * a new property `sync`.  The fs.sync property is an EventEmitter
 * which emits the following events:
 *
 * - 'error': an error occured while connecting/syncing. The error
 * object is passed as the first arg to the event.
 *
 * - 'connected': a connection was established with the sync server
 *
 * - 'disconnected': the connection to the sync server was lost, either
 * due to the client or server.
 *
 * - 'syncing': a sync with the server has begun. A subsequent 'completed'
 * or 'error' event should follow at some point, indicating whether
 * or not the sync was successful.
 *
 * - 'completed': a sync has completed and was successful.
 *
 *
 * The `sync` property also exposes a number of methods, including:
 *
 * - connect(url, [token]): try to connect to the specified sync server URL.
 * An 'error' or 'connected' event will follow, depending on success. If the
 * token parameter is provided, that authentication token will be used. Otherwise
 * the client will try to obtain one from the server's /api/sync route. This
 * requires the user to be authenticated previously with Webmaker.
 *
 * - disconnect(): disconnect from the sync server.
 *
 * - request(path): request a sync with the server for the specified
 * path. Such requests may or may not be processed right away.
 *
 *
 * Finally, the `sync` propery also exposes a `state`, which is the
 * current sync state and can be one of:
 *
 * sync.SYNC_DISCONNECTED = "SYNC DISCONNECTED" (also the initial state)
 * sync.SYNC_CONNECTING = "SYNC CONNECTING"
 * sync.SYNC_CONNECTED = "SYNC CONNECTED"
 * sync.SYNC_SYNCING = "SYNC SYNCING"
 * sync.SYNC_ERROR = "SYNC ERROR"
 */

var SyncManager = require('./sync-manager.js');
var SyncFileSystem = require('./sync-filesystem.js');
var Filer = require('../../lib/filer.js');
var resolvePath = require('../../lib/sync-path-resolver').resolve;
var EventEmitter = require('events').EventEmitter;
var request = require('request');

var MakeDrive = {};
module.exports = MakeDrive;

function createFS(options) {
  options.manual = options.manual === true;
  options.memory = options.memory === true;

  // Use a supplied provider, in memory RAM disk, or Fallback provider (default).
  var provider;
  if(options.provider) {
    provider = options.provider;
  } else if(options.memory) {
    provider = new Filer.FileSystem.providers.Memory('makedrive');
  } else {
    provider = new Filer.FileSystem.providers.Fallback('makedrive');
  }

  // Our fs instance is a modified Filer fs, with extra sync awareness
  // for conflict mediation, etc.  We keep an internal reference to the
  // raw Filer fs, and use the SyncFileSystem instance externally.
  var _fs = new Filer.FileSystem({provider: provider});
  var fs = new SyncFileSystem(_fs);
  var sync = fs.sync = new EventEmitter();
  var manager;

  // Auto-sync handles
  var autoSync;
  var pathCache;

  // State of the sync connection
  sync.SYNC_DISCONNECTED = "SYNC DISCONNECTED";
  sync.SYNC_CONNECTING = "SYNC CONNECTING";
  sync.SYNC_CONNECTED = "SYNC CONNECTED";
  sync.SYNC_SYNCING = "SYNC SYNCING";
  sync.SYNC_ERROR = "SYNC ERROR";

  // Intitially we are not connected
  sync.state = sync.SYNC_DISCONNECTED;

  function onWindowCloseHandler(event) {
    if(!options.windowCloseWarning) {
      return;
    }
    if(sync.state === sync.SYNC_SYNCING) {
      var confirmationMessage = "Sync currently underway, are you sure you want to close?";
      (event || global.event).returnValue = confirmationMessage;

      return confirmationMessage;
    }
  }

  // Turn on auto-syncing if its not already on
  sync.auto = function(interval) {
    var syncInterval = interval|0 > 0 ? interval|0 : 60 * 1000;

    if(autoSync) {
      clearInterval(autoSync);
    }

    autoSync = setInterval(sync.request, syncInterval);
  };

  // Turn off auto-syncing and turn on manual syncing
  sync.manual = function() {
    if(autoSync) {
      clearInterval(autoSync);
      autoSync = null;
    }
  };

  sync.onError = function(err) {
    // Regress to the path that needed to be synced but failed
    // (likely because of a sync LOCK)
    fs.pathToSync = pathCache;
    sync.state = sync.SYNC_ERROR;
    sync.emit('error', err);
  };

  sync.onDisconnected = function() {
    sync.state = sync.SYNC_DISCONNECTED;
    sync.emit('disconnected');
  };

  // Request that a sync begin.
  sync.request = function() {
    // If we're not connected (or are already syncing), ignore this request
    if(sync.state === sync.SYNC_DISCONNECTED || sync.state === sync.SYNC_ERROR) {
      sync.emit('error', new Error('Invalid state. Expected ' + sync.SYNC_CONNECTED + ', got ' + sync.state));
      return;
    }

    // If there were no changes to the filesystem, ignore this request
    if(!fs.pathToSync) {
      return;
    }

    // Cache the path that needs to be synced for error recovery
    pathCache = fs.pathToSync;
    fs.pathToSync = null;
    manager.syncPath(pathCache);
  };

  // Try to connect to the server.
  sync.connect = function(url, token) {
    // Bail if we're already connected
    if(sync.state !== sync.SYNC_DISCONNECTED &&
       sync.state !== sync.ERROR) {
      sync.emit('error', new Error("MakeDrive: Attempted to connect to \"" + url + "\", but a connection already exists!"));
      return;
    }

    // Also bail if we already have a SyncManager
    if(manager) {
      return;
    }

    // Upgrade connection state to `connecting`
    sync.state = sync.SYNC_CONNECTING;

    function downstreamSyncCompleted() {
      // Re-wire message handler functions for regular syncing
      // now that initial downstream sync is completed.
      sync.onSyncing = function() {
        sync.state = sync.SYNC_SYNCING;
        sync.emit('syncing');
      };

      sync.onCompleted = function(paths) {
        // If changes happened to the files that needed to be synced
        // during the sync itself, they will be overwritten
        // https://github.com/mozilla/makedrive/issues/129 and
        // https://github.com/mozilla/makedrive/issues/3

        function complete() {
          sync.state = sync.SYNC_CONNECTED;
          sync.emit('completed');
        }

        if(!paths) {
          return complete();
        }

        manager.resetUnsynced(paths, function(err) {
          if(err) {
            return sync.onError(err);
          }

          complete();
        });
      };

      // Upgrade connection state to 'connected'
      sync.state = sync.SYNC_CONNECTED;

      // If we're in manual mode, bail before starting auto-sync
      if(options.manual) {
        sync.manual();
      } else {
        sync.auto(options.interval);
      }

      sync.emit('connected');
    }

    function connect(token) {
      // Try to connect to provided server URL. Use the raw Filer fs
      // instance for all rsync operations on the filesystem, so that we
      // can untangle changes done by user vs. sync code.
      manager = new SyncManager(sync, _fs);
      manager.init(url, token, function(err) {
        if(err) {
          sync.onError(err);
          return;
        }

        // Wait on initial downstream sync events to complete
        sync.onSyncing = function() {
          // do nothing, wait for onCompleted()
        };
        sync.onCompleted = function() {
          // Downstream sync is done, finish connect() setup
          downstreamSyncCompleted();
        };
      });
    }

    // In a browser, try to clean-up after ourselves when window goes away
    if("onbeforeunload" in global) {
      global.addEventListener('beforeunload', onWindowCloseHandler);
    }

    if("onunload" in global){
      global.addEventListener('unload', function(event) {
        if(manager) {
          manager.close();
          manager = null;
        }
      });
    }

    // If we were provided a token, we can connect right away, otherwise
    // we need to get one first via the /api/sync route
    if(token) {
      connect(token);
    } else {
      // Remove WebSocket protocol from URL, and swap for http:// or https://
      // ws://drive.webmaker.org/ -> http://drive.webmaker.org/api/sync
      var apiSync = url.replace(/^([^\/]*\/\/)?/, function(match, p1) {
        return p1 === 'wss://' ? 'https://' : 'http://';
      });
      // Also add /api/sync to the end:
      apiSync = apiSync.replace(/\/?$/, '/api/sync');

      request({
        url: apiSync,
        method: 'GET',
        json: true,
        withCredentials: true
      }, function(err, msg, body) {
        var statusCode;
        var error;

        statusCode = msg && msg.statusCode;
        error = statusCode !== 200 ?
          { message: err || 'Unable to get token', code: statusCode } : null;

        if(error) {
          sync.onError(error);
        } else {
          connect(body);
        }
      });
    }
  };

  // Disconnect from the server
  sync.disconnect = function() {
    // Remove our browser cleanup
    if("onbeforeunload" in global && sync.cleanupFn) {
      global.removeEventListener('beforeunload', sync.cleanupFn);
      sync.cleanupFn = null;
    }

    // Bail if we're not already connected
    if(sync.state === sync.SYNC_DISCONNECTED ||
       sync.state === sync.ERROR) {
      sync.emit('error', new Error("MakeDrive: Attempted to disconnect, but no server connection exists!"));
      return;
    }

    // Do a proper shutdown
    if(manager) {
      manager.close();
      manager = null;
    }

    // Stop auto-syncing
    if(autoSync) {
      clearInterval(autoSync);
      autoSync = null;
      fs.pathToSync = null;
    }

    sync.onDisconnected();
  };

  return fs;
}

// Manage single instance of a Filer filesystem with auto-sync'ing
var sharedFS;

MakeDrive.fs = function(options) {
  options = options || {};

  // We usually only want to hand out a single, shared instance
  // for every call, but sometimes you need multiple (e.g., tests)
  if(options.forceCreate) {
    return createFS(options);
  }

  if(!sharedFS) {
    sharedFS = createFS(options);
  }
  return sharedFS;
};

// Expose bits of Filer that clients will need on MakeDrive
MakeDrive.Buffer = Filer.Buffer;
MakeDrive.Path = Filer.Path;
MakeDrive.Errors = Filer.Errors;
<commit_msg>Fix #325 - don't leak references in client shutdown code<commit_after>/**
 * MakeDrive is a single/shared Filer filesystem instance with
 * manual- and auto-sync'ing features. A client first gets the
 * filesystem instance like so:
 *
 * var fs = MakeDrive.fs();
 *
 * Multiple calls to MakeDrive.fs() will return the same instance.
 *
 * A number of configuration options can be passed to the fs() function.
 * These include:
 *
 * - manual=true - by default the filesystem syncs automatically in
 * the background. This disables it.
 *
 * - memory=<Boolean> - by default we use a persistent store (indexeddb
 * or websql). Using memory=true overrides and uses a temporary ram disk.
 *
 * - provider=<Object> - a Filer data provider to use instead of the
 * default provider normally used. The provider given should already
 * be instantiated (i.e., don't pass a constructor function).
 *
 * - forceCreate=<Boolean> - by default we return the same fs instance with
 * every call to MakeDrive.fs(). In some cases it is necessary to have
 * multiple instances.  Using forceCreate=true does this.
 *
 * - interval=<Number> - by default, the filesystem syncs every minute if
 * auto syncing is turned on otherwise the interval between syncs can be
 * specified in ms.
 *
 * Various bits of Filer are available on MakeDrive, including:
 *
 * - MakeDrive.Buffer
 * - MakeDrive.Path
 * - MakeDrive.Errors
 *
 * The filesystem instance returned by MakeDrive.fs() also includes
 * a new property `sync`.  The fs.sync property is an EventEmitter
 * which emits the following events:
 *
 * - 'error': an error occured while connecting/syncing. The error
 * object is passed as the first arg to the event.
 *
 * - 'connected': a connection was established with the sync server
 *
 * - 'disconnected': the connection to the sync server was lost, either
 * due to the client or server.
 *
 * - 'syncing': a sync with the server has begun. A subsequent 'completed'
 * or 'error' event should follow at some point, indicating whether
 * or not the sync was successful.
 *
 * - 'completed': a sync has completed and was successful.
 *
 *
 * The `sync` property also exposes a number of methods, including:
 *
 * - connect(url, [token]): try to connect to the specified sync server URL.
 * An 'error' or 'connected' event will follow, depending on success. If the
 * token parameter is provided, that authentication token will be used. Otherwise
 * the client will try to obtain one from the server's /api/sync route. This
 * requires the user to be authenticated previously with Webmaker.
 *
 * - disconnect(): disconnect from the sync server.
 *
 * - request(path): request a sync with the server for the specified
 * path. Such requests may or may not be processed right away.
 *
 *
 * Finally, the `sync` propery also exposes a `state`, which is the
 * current sync state and can be one of:
 *
 * sync.SYNC_DISCONNECTED = "SYNC DISCONNECTED" (also the initial state)
 * sync.SYNC_CONNECTING = "SYNC CONNECTING"
 * sync.SYNC_CONNECTED = "SYNC CONNECTED"
 * sync.SYNC_SYNCING = "SYNC SYNCING"
 * sync.SYNC_ERROR = "SYNC ERROR"
 */

var SyncManager = require('./sync-manager.js');
var SyncFileSystem = require('./sync-filesystem.js');
var Filer = require('../../lib/filer.js');
var resolvePath = require('../../lib/sync-path-resolver').resolve;
var EventEmitter = require('events').EventEmitter;
var request = require('request');

var MakeDrive = {};
module.exports = MakeDrive;

function createFS(options) {
  options.manual = options.manual === true;
  options.memory = options.memory === true;

  // Use a supplied provider, in memory RAM disk, or Fallback provider (default).
  var provider;
  if(options.provider) {
    provider = options.provider;
  } else if(options.memory) {
    provider = new Filer.FileSystem.providers.Memory('makedrive');
  } else {
    provider = new Filer.FileSystem.providers.Fallback('makedrive');
  }

  // Our fs instance is a modified Filer fs, with extra sync awareness
  // for conflict mediation, etc.  We keep an internal reference to the
  // raw Filer fs, and use the SyncFileSystem instance externally.
  var _fs = new Filer.FileSystem({provider: provider});
  var fs = new SyncFileSystem(_fs);
  var sync = fs.sync = new EventEmitter();
  var manager;

  // Auto-sync handles
  var autoSync;
  var pathCache;

  // State of the sync connection
  sync.SYNC_DISCONNECTED = "SYNC DISCONNECTED";
  sync.SYNC_CONNECTING = "SYNC CONNECTING";
  sync.SYNC_CONNECTED = "SYNC CONNECTED";
  sync.SYNC_SYNCING = "SYNC SYNCING";
  sync.SYNC_ERROR = "SYNC ERROR";

  // Intitially we are not connected
  sync.state = sync.SYNC_DISCONNECTED;

  // Optionally warn when closing the window if still syncing
  function windowCloseHandler(event) {
    if(!options.windowCloseWarning) {
      return;
    }

    if(sync.state !== sync.SYNC_SYNCING) {
      return;
    }

    var confirmationMessage = "Sync currently underway, are you sure you want to close?";
    (event || global.event).returnValue = confirmationMessage;

    return confirmationMessage;
  }

  function cleanupManager() {
    if(!manager) {
      return;
    }
    manager.close();
    manager = null;
  }

  // Turn on auto-syncing if its not already on
  sync.auto = function(interval) {
    var syncInterval = interval|0 > 0 ? interval|0 : 60 * 1000;

    if(autoSync) {
      clearInterval(autoSync);
    }

    autoSync = setInterval(sync.request, syncInterval);
  };

  // Turn off auto-syncing and turn on manual syncing
  sync.manual = function() {
    if(autoSync) {
      clearInterval(autoSync);
      autoSync = null;
    }
  };

  sync.onError = function(err) {
    // Regress to the path that needed to be synced but failed
    // (likely because of a sync LOCK)
    fs.pathToSync = pathCache;
    sync.state = sync.SYNC_ERROR;
    sync.emit('error', err);
  };

  sync.onDisconnected = function() {
    // Remove listeners so we don't leak instance variables
    if("onbeforeunload" in global) {
      global.removeEventListener('beforeunload', windowCloseHandler);
    }
    if("onunload" in global){
      global.removeEventListener('unload', cleanupManager);
    }

    sync.state = sync.SYNC_DISCONNECTED;
    sync.emit('disconnected');
  };

  // Request that a sync begin.
  sync.request = function() {
    // If we're not connected (or are already syncing), ignore this request
    if(sync.state === sync.SYNC_DISCONNECTED || sync.state === sync.SYNC_ERROR) {
      sync.emit('error', new Error('Invalid state. Expected ' + sync.SYNC_CONNECTED + ', got ' + sync.state));
      return;
    }

    // If there were no changes to the filesystem, ignore this request
    if(!fs.pathToSync) {
      return;
    }

    // Cache the path that needs to be synced for error recovery
    pathCache = fs.pathToSync;
    fs.pathToSync = null;
    manager.syncPath(pathCache);
  };

  // Try to connect to the server.
  sync.connect = function(url, token) {
    // Bail if we're already connected
    if(sync.state !== sync.SYNC_DISCONNECTED &&
       sync.state !== sync.ERROR) {
      sync.emit('error', new Error("MakeDrive: Attempted to connect to \"" + url + "\", but a connection already exists!"));
      return;
    }

    // Also bail if we already have a SyncManager
    if(manager) {
      return;
    }

    // Upgrade connection state to `connecting`
    sync.state = sync.SYNC_CONNECTING;

    function downstreamSyncCompleted() {
      // Re-wire message handler functions for regular syncing
      // now that initial downstream sync is completed.
      sync.onSyncing = function() {
        sync.state = sync.SYNC_SYNCING;
        sync.emit('syncing');
      };

      sync.onCompleted = function(paths) {
        // If changes happened to the files that needed to be synced
        // during the sync itself, they will be overwritten
        // https://github.com/mozilla/makedrive/issues/129 and
        // https://github.com/mozilla/makedrive/issues/3

        function complete() {
          sync.state = sync.SYNC_CONNECTED;
          sync.emit('completed');
        }

        if(!paths) {
          return complete();
        }

        manager.resetUnsynced(paths, function(err) {
          if(err) {
            return sync.onError(err);
          }

          complete();
        });
      };

      // Upgrade connection state to 'connected'
      sync.state = sync.SYNC_CONNECTED;

      // If we're in manual mode, bail before starting auto-sync
      if(options.manual) {
        sync.manual();
      } else {
        sync.auto(options.interval);
      }

      // In a browser, try to clean-up after ourselves when window goes away
      if("onbeforeunload" in global) {
        global.addEventListener('beforeunload', windowCloseHandler);
      }
      if("onunload" in global){
        global.addEventListener('unload', cleanupManager);
      }

      sync.emit('connected');
    }

    function connect(token) {
      // Try to connect to provided server URL. Use the raw Filer fs
      // instance for all rsync operations on the filesystem, so that we
      // can untangle changes done by user vs. sync code.
      manager = new SyncManager(sync, _fs);
      manager.init(url, token, function(err) {
        if(err) {
          sync.onError(err);
          return;
        }

        // Wait on initial downstream sync events to complete
        sync.onSyncing = function() {
          // do nothing, wait for onCompleted()
        };
        sync.onCompleted = function() {
          // Downstream sync is done, finish connect() setup
          downstreamSyncCompleted();
        };
      });
    }

    // If we were provided a token, we can connect right away, otherwise
    // we need to get one first via the /api/sync route
    if(token) {
      connect(token);
    } else {
      // Remove WebSocket protocol from URL, and swap for http:// or https://
      // ws://drive.webmaker.org/ -> http://drive.webmaker.org/api/sync
      var apiSync = url.replace(/^([^\/]*\/\/)?/, function(match, p1) {
        return p1 === 'wss://' ? 'https://' : 'http://';
      });
      // Also add /api/sync to the end:
      apiSync = apiSync.replace(/\/?$/, '/api/sync');

      request({
        url: apiSync,
        method: 'GET',
        json: true,
        withCredentials: true
      }, function(err, msg, body) {
        var statusCode;
        var error;

        statusCode = msg && msg.statusCode;
        error = statusCode !== 200 ?
          { message: err || 'Unable to get token', code: statusCode } : null;

        if(error) {
          sync.onError(error);
        } else {
          connect(body);
        }
      });
    }
  };

  // Disconnect from the server
  sync.disconnect = function() {
    // Bail if we're not already connected
    if(sync.state === sync.SYNC_DISCONNECTED ||
       sync.state === sync.ERROR) {
      sync.emit('error', new Error("MakeDrive: Attempted to disconnect, but no server connection exists!"));
      return;
    }

    // Stop auto-syncing
    if(autoSync) {
      clearInterval(autoSync);
      autoSync = null;
      fs.pathToSync = null;
    }

    // Do a proper network shutdown
    cleanupManager();

    sync.onDisconnected();
  };

  return fs;
}

// Manage single instance of a Filer filesystem with auto-sync'ing
var sharedFS;

MakeDrive.fs = function(options) {
  options = options || {};

  // We usually only want to hand out a single, shared instance
  // for every call, but sometimes you need multiple (e.g., tests)
  if(options.forceCreate) {
    return createFS(options);
  }

  if(!sharedFS) {
    sharedFS = createFS(options);
  }
  return sharedFS;
};

// Expose bits of Filer that clients will need on MakeDrive
MakeDrive.Buffer = Filer.Buffer;
MakeDrive.Path = Filer.Path;
MakeDrive.Errors = Filer.Errors;
<|endoftext|>


<commit_before>// Set initial game state.
levels = []; // : { map: { [row: number]: { [col: number]: string; }; }, width: number; height: number; }
currentLevel = 0;
lives = 3;
keys = 0;

// Start the game when the page has loaded.
$(document).ready(function () {
    parseLevelMaps();
    showCurrentLives();
    loadCurrentLevel();
    enableGamepad();
    startGameLoop();
    $('#credits-show').click(function() { $('#credits-popup').show(); });
    $('#credits-hide').click(function() { $('#credits-popup').hide(); });
});

function parseLevelMaps() {
    var rawLevelMaps = $('#level-maps').text();
    var rawLevels = rawLevelMaps.split(/L[0-9]+[ ]*\r?\n/).slice(1);
    for (var i = 0; i < rawLevels.length; ++i) {
        var rawLevel = rawLevels[i];
        var lines = rawLevel.split(/\r?\n/);
        while (!lines[lines.length - 1]) lines.pop();
        var longestLine = 0;
        for (var j = 0; j < lines.length; ++j) {
            if (lines[j].length > longestLine) longestLine = lines[j].length;
        }
        for (var j = 0; j < lines.length; ++j) {
            var padding = longestLine - lines[j].length;
            for (var k = 0; k < padding; ++k) lines[j] += ' ';
        }
        var rows = [];
        for (var j = 0; j < lines.length; ++j) {
            rows.push(lines[j].split(''));
        }

        levels[i] = {
            width: longestLine,
            height: lines.length,
            map: rows
        };
    }
}

var gamepad = {
    UP: { isDown: false },
    DOWN: { isDown: false },
    LEFT: { isDown: false },
    RIGHT: { isDown: false }
}

function enableGamepad() {
    $('#gamepad .up').on('mousedown touchstart', function (e) { gamepad.UP.isDown = true; e.preventDefault(); });
    $('#gamepad .up').on('mouseup mouseout touchend', function (e) { gamepad.UP.isDown = false; e.preventDefault(); });
    $('#gamepad .down').on('mousedown touchstart', function (e) { gamepad.DOWN.isDown = true; e.preventDefault(); });
    $('#gamepad .down').on('mouseup mouseout touchend', function (e) { gamepad.DOWN.isDown = false; e.preventDefault(); });
    $('#gamepad .left').on('mousedown touchstart', function (e) { gamepad.LEFT.isDown = true; e.preventDefault(); });
    $('#gamepad .left').on('mouseup mouseout touchend', function (e) { gamepad.LEFT.isDown = false; e.preventDefault(); });
    $('#gamepad .right').on('mousedown touchstart', function (e) { gamepad.RIGHT.isDown = true; e.preventDefault(); });
    $('#gamepad .right').on('mouseup mouseout touchend', function (e) { gamepad.RIGHT.isDown = false; e.preventDefault(); });
}

function loadCurrentLevel() {

    // Show the current level in the titlebar.
    $('#level').text(currentLevel + 1);

    // Create all divs for maze rows and cells, with classes for objects in the maze.
    var level = levels[currentLevel];
    var mazeHTML = '

';
    for (var y = 0; y < level.height; ++y) {
        var row = '
';
        for (var x = 0; x < level.width; ++x) {
            var cell = '

';
            row += cell;
        }
        row += '
';
        mazeHTML += row;
    }
    $('#cave')
    .html(mazeHTML)
    .width(32 * level.width)
    .height(32 * level.height);

    // TODO: temp testing... generalise this depending on factors
    $('#cave-outer').scrollTop(256).scrollLeft(256);
}

function startGameLoop() {
    setTimeout(update, 100);
}

var player = {
    x: 1,
    y: 1,
    move: function(dx, dy) {
        var x = player.x = player.x + dx;
        var y = player.y = player.y + dy;
        var left = (x * 32) + 'px';
        var top = (y * 32) + 'px';
        $('#player').css({ 'margin-left': left, 'margin-top': top});
    }
}

function isRocksAt(x, y) {
    return levels[currentLevel].map[y][x] ==='#';
}
function isBlockedAt(x, y) {
    var p = levels[currentLevel].map[y][x];
    return p ==='#' || p === 'D';
}
function isKeyAt(x, y) {
    return levels[currentLevel].map[y][x] ==='K';
}
function isDoorAt(x, y) {
    return levels[currentLevel].map[y][x] ==='D';
}

function update() {
    var level = levels[currentLevel];

    // Move/draw player
    var UP = kd.UP.isDown() || gamepad.UP.isDown;
    var DOWN = kd.DOWN.isDown() || gamepad.DOWN.isDown;
    var LEFT = kd.LEFT.isDown() || gamepad.LEFT.isDown;
    var RIGHT = kd.RIGHT.isDown() || gamepad.RIGHT.isDown;
    var dx = LEFT ? -1 : RIGHT ? 1 : 0;
    var dy = UP ? -1 : DOWN ? 1 : 0;

    // Hit door?
    if (isDoorAt(player.x + dx, player.y + dy)) {
        if (keys > 0) {
            --keys;
            level.map[player.x + dy, player.y + dy] = ' ';
            $('#cave>div:nth-child(' + (player.y + dy + 2) + ')>div:nth-child(' + (player.x + dx + 1) + ')').removeClass('door-gold');
        }
    }

    // Hit wall, door, etc?
    if (isBlockedAt(player.x + dx, player.y + dy)) {
        if (dx != 0 && dy != 0) {
            if (!isRocksAt(player.x + dx, player.y))        dy = 0;
            else if (!isRocksAt(player.x, player.y + dy))   dx = 0;
            else                                            dx = dy = 0;
        }
        else {
            dx = dy = 0;
        }
    }
    player.move(dx, dy);
    if ((player.x > 6 && dx > 0) || (player.x < (level.width - 6) && dx < 0)) {
        $('#cave-outer').scrollLeft($('#cave-outer').scrollLeft() + dx * 32);
    }
    if ((player.y > 6 && dy > 0) || (player.y < (level.height - 6) && dy < 0)) {
        $('#cave-outer').scrollTop($('#cave-outer').scrollTop() + dy * 32);
    }
    dx = dy = 0;

    // Hit key?
    if (isKeyAt(player.x, player.y)) {
        ++keys;
        level.map[player.x, player.y] = ' ';
        $('#cave>div:nth-child(' + (player.y + 2) + ')>div:nth-child(' + (player.x + 1) + ')').removeClass('key-gold');
    }

    //// Fell in a hole?
    //if (isHoleAt(player.x, player.y)) {

    //    // Player died!
    //    --lives;
    //    $('#cave').addClass('died');
    //    setTimeout(function () {
    //        if (lives <= 0) {

    //            // Player loses game!
    //            alert('Oh no! You lose the game!!! Better luck next time.');
    //            lives = 3;
    //            currentLevel = 0;
    //        }
    //        else {
    //            alert('Aaa! You lose a life!');
    //        }
    //        $('#cave').removeClass('died');
    //        showCurrentLives();
    //        loadCurrentLevel();
    //        startGameLoop();
    //    }, 1000);
    //}

    //// Found treasure?
    //else if (isTreasureAt(player.x, player.y)) {

    //    // Player wins level!
    //    $('#player').addClass('wins');
    //    $('#cave').addClass('wins');

    //    setTimeout(function() {
    //        ++currentLevel;
    //        if (currentLevel >= levels.length) {

    //            // Player wins game!
    //            alert('YOU\'RE THE CAVE MAN CHAMPION!!!');
    //            currentLevel = 0;
    //        }
    //        $('#cave').removeClass('wins');
    //        loadCurrentLevel();
    //        startGameLoop();
    //    }, 2000);

    //}

    // Nothing happened, keep playing....
    //else {
        setTimeout(update, 100);
    //}
}

function showCurrentLives() {
    for (var i = 1; i <= 3; ++i) {
        var id = '#life' + i;
        if (lives >= i) $(id).show(); else $(id).hide();
    }
}
<commit_msg>bugfixes for key/door logic<commit_after>// Set initial game state.
levels = []; // : { map: { [row: number]: { [col: number]: string; }; }, width: number; height: number; }
currentLevel = 0;
lives = 3;
keys = 0;

// Start the game when the page has loaded.
$(document).ready(function () {
    parseLevelMaps();
    showCurrentLives();
    loadCurrentLevel();
    enableGamepad();
    startGameLoop();
    $('#credits-show').click(function() { $('#credits-popup').show(); });
    $('#credits-hide').click(function() { $('#credits-popup').hide(); });
});

function parseLevelMaps() {
    var rawLevelMaps = $('#level-maps').text();
    var rawLevels = rawLevelMaps.split(/L[0-9]+[ ]*\r?\n/).slice(1);
    for (var i = 0; i < rawLevels.length; ++i) {
        var rawLevel = rawLevels[i];
        var lines = rawLevel.split(/\r?\n/);
        while (!lines[lines.length - 1]) lines.pop();
        var longestLine = 0;
        for (var j = 0; j < lines.length; ++j) {
            if (lines[j].length > longestLine) longestLine = lines[j].length;
        }
        for (var j = 0; j < lines.length; ++j) {
            var padding = longestLine - lines[j].length;
            for (var k = 0; k < padding; ++k) lines[j] += ' ';
        }
        var rows = [];
        for (var j = 0; j < lines.length; ++j) {
            rows.push(lines[j].split(''));
        }

        levels[i] = {
            width: longestLine,
            height: lines.length,
            map: rows
        };
    }
}

var gamepad = {
    UP: { isDown: false },
    DOWN: { isDown: false },
    LEFT: { isDown: false },
    RIGHT: { isDown: false }
}

function enableGamepad() {
    $('#gamepad .up').on('mousedown touchstart', function (e) { gamepad.UP.isDown = true; e.preventDefault(); });
    $('#gamepad .up').on('mouseup mouseout touchend', function (e) { gamepad.UP.isDown = false; e.preventDefault(); });
    $('#gamepad .down').on('mousedown touchstart', function (e) { gamepad.DOWN.isDown = true; e.preventDefault(); });
    $('#gamepad .down').on('mouseup mouseout touchend', function (e) { gamepad.DOWN.isDown = false; e.preventDefault(); });
    $('#gamepad .left').on('mousedown touchstart', function (e) { gamepad.LEFT.isDown = true; e.preventDefault(); });
    $('#gamepad .left').on('mouseup mouseout touchend', function (e) { gamepad.LEFT.isDown = false; e.preventDefault(); });
    $('#gamepad .right').on('mousedown touchstart', function (e) { gamepad.RIGHT.isDown = true; e.preventDefault(); });
    $('#gamepad .right').on('mouseup mouseout touchend', function (e) { gamepad.RIGHT.isDown = false; e.preventDefault(); });
}

function loadCurrentLevel() {

    // Show the current level in the titlebar.
    $('#level').text(currentLevel + 1);

    // Create all divs for maze rows and cells, with classes for objects in the maze.
    var level = levels[currentLevel];
    var mazeHTML = '

';
    for (var y = 0; y < level.height; ++y) {
        var row = '
';
        for (var x = 0; x < level.width; ++x) {
            var cell = '

';
            row += cell;
        }
        row += '
';
        mazeHTML += row;
    }
    $('#cave')
    .html(mazeHTML)
    .width(32 * level.width)
    .height(32 * level.height);

    // TODO: temp testing... generalise this depending on factors
    $('#cave-outer').scrollTop(256).scrollLeft(256);
}

function startGameLoop() {
    setTimeout(update, 100);
}

var player = {
    x: 1,
    y: 1,
    move: function(dx, dy) {
        var x = player.x = player.x + dx;
        var y = player.y = player.y + dy;
        var left = (x * 32) + 'px';
        var top = (y * 32) + 'px';
        $('#player').css({ 'margin-left': left, 'margin-top': top});
    }
}

function isRocksAt(x, y) {
    return levels[currentLevel].map[y][x] ==='#';
}
function isBlockedAt(x, y) {
    var p = levels[currentLevel].map[y][x];
    return p ==='#' || p === 'D';
}
function isKeyAt(x, y) {
    return levels[currentLevel].map[y][x] ==='K';
}
function isDoorAt(x, y) {
    return levels[currentLevel].map[y][x] ==='D';
}

function update() {
    var level = levels[currentLevel];

    // Move/draw player
    var UP = kd.UP.isDown() || gamepad.UP.isDown;
    var DOWN = kd.DOWN.isDown() || gamepad.DOWN.isDown;
    var LEFT = kd.LEFT.isDown() || gamepad.LEFT.isDown;
    var RIGHT = kd.RIGHT.isDown() || gamepad.RIGHT.isDown;
    var dx = LEFT ? -1 : RIGHT ? 1 : 0;
    var dy = UP ? -1 : DOWN ? 1 : 0;

    // Hit door?
    if (isDoorAt(player.x + dx, player.y + dy)) {
        if (keys > 0) {
            --keys;
            level.map[player.y + dy][player.x + dx] = ' ';
            $('#cave>div:nth-child(' + (player.y + dy + 2) + ')>div:nth-child(' + (player.x + dx + 1) + ')').removeClass('door-gold');
        }
    }

    // Hit wall, door, etc?
    if (isBlockedAt(player.x + dx, player.y + dy)) {
        if (dx != 0 && dy != 0) {
            if (!isBlockedAt(player.x + dx, player.y))          dy = 0;
            else if (!isBlockedAt(player.x, player.y + dy))     dx = 0;
            else                                                dx = dy = 0;
        }
        else {
            dx = dy = 0;
        }
    }
    player.move(dx, dy);
    if ((player.x > 6 && dx > 0) || (player.x < (level.width - 6) && dx < 0)) {
        $('#cave-outer').scrollLeft($('#cave-outer').scrollLeft() + dx * 32);
    }
    if ((player.y > 6 && dy > 0) || (player.y < (level.height - 6) && dy < 0)) {
        $('#cave-outer').scrollTop($('#cave-outer').scrollTop() + dy * 32);
    }
    dx = dy = 0;

    // Hit key?
    if (isKeyAt(player.x, player.y)) {
        ++keys;
        level.map[player.y][player.x] = ' ';
        $('#cave>div:nth-child(' + (player.y + 2) + ')>div:nth-child(' + (player.x + 1) + ')').removeClass('key-gold');
    }

    //// Fell in a hole?
    //if (isHoleAt(player.x, player.y)) {

    //    // Player died!
    //    --lives;
    //    $('#cave').addClass('died');
    //    setTimeout(function () {
    //        if (lives <= 0) {

    //            // Player loses game!
    //            alert('Oh no! You lose the game!!! Better luck next time.');
    //            lives = 3;
    //            currentLevel = 0;
    //        }
    //        else {
    //            alert('Aaa! You lose a life!');
    //        }
    //        $('#cave').removeClass('died');
    //        showCurrentLives();
    //        loadCurrentLevel();
    //        startGameLoop();
    //    }, 1000);
    //}

    //// Found treasure?
    //else if (isTreasureAt(player.x, player.y)) {

    //    // Player wins level!
    //    $('#player').addClass('wins');
    //    $('#cave').addClass('wins');

    //    setTimeout(function() {
    //        ++currentLevel;
    //        if (currentLevel >= levels.length) {

    //            // Player wins game!
    //            alert('YOU\'RE THE CAVE MAN CHAMPION!!!');
    //            currentLevel = 0;
    //        }
    //        $('#cave').removeClass('wins');
    //        loadCurrentLevel();
    //        startGameLoop();
    //    }, 2000);

    //}

    // Nothing happened, keep playing....
    //else {
        setTimeout(update, 100);
    //}
}

function showCurrentLives() {
    for (var i = 1; i <= 3; ++i) {
        var id = '#life' + i;
        if (lives >= i) $(id).show(); else $(id).hide();
    }
}
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

import './index.css';
import App from './App';
import state from './state';

// This is where all the application state is kept
const store = createStore(state, applyMiddleware(thunk));

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root'),
);
<commit_msg>enable code hot loading<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

import './index.css';
import App from './App';
import state from './state';

// This is where all the application state is kept
const store = createStore(state, applyMiddleware(thunk));
const rootEl = document.getElementById('root');

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootEl,
);

if (module.hot) {
  module.hot.accept('./App', () => {
    const NextApp = require('./App').default;
    ReactDOM.render(
      <Provider store={store}>
        <NextApp />
      </Provider>,
      rootEl,
    );
  });
}
<|endoftext|>


<commit_before>import assertString from './util/assertString';

/* eslint-disable max-len */
const phones = {
  'ar-AE': /^((\+?971)|0)?5[024568]\d{7}$/,
  'ar-DZ': /^(\+?213|0)(5|6|7)\d{8}$/,
  'ar-EG': /^((\+?20)|0)?1[012]\d{8}$/,
  'ar-IQ': /^(\+?964|0)?7[0-9]\d{8}$/,
  'ar-JO': /^(\+?962|0)?7[789]\d{7}$/,
  'ar-KW': /^(\+?965)[569]\d{7}$/,
  'ar-SA': /^(!?(\+?966)|0)?5\d{8}$/,
  'ar-SY': /^(!?(\+?963)|0)?9\d{8}$/,
  'ar-TN': /^(\+?216)?[2459]\d{7}$/,
  'be-BY': /^(\+?375)?(24|25|29|33|44)\d{7}$/,
  'bg-BG': /^(\+?359|0)?8[789]\d{7}$/,
  'cs-CZ': /^(\+?420)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/,
  'da-DK': /^(\+?45)?\s?\d{2}\s?\d{2}\s?\d{2}\s?\d{2}$/,
  'de-DE': /^(\+?49[ \.\-]?)?([\(]{1}[0-9]{1,6}[\)])?([0-9 \.\-\/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$/,
  'el-GR': /^(\+?30|0)?(69\d{8})$/,
  'en-AU': /^(\+?61|0)4\d{8}$/,
  'en-GB': /^(\+?44|0)7\d{9}$/,
  'en-HK': /^(\+?852\-?)?[456789]\d{3}\-?\d{4}$/,
  'en-IN': /^(\+?91|0)?[6789]\d{9}$/,
  'en-KE': /^(\+?254|0)?[7]\d{8}$/,
  'en-NG': /^(\+?234|0)?[789]\d{9}$/,
  'en-NZ': /^(\+?64|0)2\d{7,9}$/,
  'en-PK': /^((\+92)|(0092))-{0,1}\d{3}-{0,1}\d{7}$|^\d{11}$|^\d{4}-\d{7}$/,
  'en-RW': /^(\+?250|0)?[7]\d{8}$/,
  'en-SG': /^(\+65)?[89]\d{7}$/,
  'en-TZ': /^(\+?255|0)?[67]\d{8}$/,
  'en-UG': /^(\+?256|0)?[7]\d{8}$/,
  'en-US': /^(\+?1)?[2-9]\d{2}[2-9](?!11)\d{6}$/,
  'en-ZA': /^(\+?27|0)\d{9}$/,
  'en-ZM': /^(\+?26)?09[567]\d{7}$/,
  'es-ES': /^(\+?34)?(6\d{1}|7[1234])\d{7}$/,
  'et-EE': /^(\+?372)?\s?(5|8[1-4])\s?([0-9]\s?){6,7}$/,
  'fa-IR': /^(\+?98[\-\s]?|0)9[0-39]\d[\-\s]?\d{3}[\-\s]?\d{4}$/,
  'fi-FI': /^(\+?358|0)\s?(4(0|1|2|4|5|6)?|50)\s?(\d\s?){4,8}\d$/,
  'fo-FO': /^(\+?298)?\s?\d{2}\s?\d{2}\s?\d{2}$/,
  'fr-FR': /^(\+?33|0)[67]\d{8}$/,
  'he-IL': /^(\+972|0)([23489]|5[012345689]|77)[1-9]\d{6}/,
  'hu-HU': /^(\+?36)(20|30|70)\d{7}$/,
  'id-ID': /^(\+?62|0[1-9])[\s|\d]+$/,
  'it-IT': /^(\+?39)?\s?3\d{2} ?\d{6,7}$/,
  'ja-JP': /^(\+?81|0)[789]0[ \-]?[1-9]\d{2}[ \-]?\d{5}$/,
  'kk-KZ': /^(\+?7|8)?7\d{9}$/,
  'kl-GL': /^(\+?299)?\s?\d{2}\s?\d{2}\s?\d{2}$/,
  'ko-KR': /^((\+?82)[ \-]?)?0?1([0|1|6|7|8|9]{1})[ \-]?\d{3,4}[ \-]?\d{4}$/,
  'lt-LT': /^(\+370|8)\d{8}$/,
  'ms-MY': /^(\+?6?01){1}(([145]{1}(\-|\s)?\d{7,8})|([236789]{1}(\s|\-)?\d{7}))$/,
  'nb-NO': /^(\+?47)?[49]\d{7}$/,
  'nl-BE': /^(\+?32|0)4?\d{8}$/,
  'nn-NO': /^(\+?47)?[49]\d{7}$/,
  'pl-PL': /^(\+?48)? ?[5-8]\d ?\d{3} ?\d{2} ?\d{2}$/,
  'pt-BR': /^(\+?55|0)\-?[1-9]{2}\-?[2-9]{1}\d{3,4}\-?\d{4}$/,
  'pt-PT': /^(\+?351)?9[1236]\d{7}$/,
  'ro-RO': /^(\+?4?0)\s?7\d{2}(\/|\s|\.|\-)?\d{3}(\s|\.|\-)?\d{3}$/,
  'ru-RU': /^(\+?7|8)?9\d{9}$/,
  'sk-SK': /^(\+?421)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/,
  'sr-RS': /^(\+3816|06)[- \d]{5,9}$/,
  'sv-SE': /^(\+?46|0)[\s\-]?7[\s\-]?[02369]([\s\-]?\d){7}$/,
  'th-TH': /^(\+66|66|0)\d{9}$/,
  'tr-TR': /^(\+?90|0)?5\d{9}$/,
  'uk-UA': /^(\+?38|8)?0\d{9}$/,
  'vi-VN': /^(\+?84|0)?((1(2([0-9])|6([2-9])|88|99))|(9((?!5)[0-9])))([0-9]{7})$/,
  'zh-CN': /^(\+?0?86\-?)?1[3456789]\d{9}$/,
  'zh-TW': /^(\+?886\-?|0)?9\d{8}$/,
};
/* eslint-enable max-len */

// aliases
phones['en-CA'] = phones['en-US'];
phones['fr-BE'] = phones['nl-BE'];
phones['zh-HK'] = phones['en-HK'];

export default function isMobilePhone(str, locale, options) {
  assertString(str);
  if (options && options.strictMode && !str.startsWith('+')) {
    return false;
  }
  if (Array.isArray(locale)) {
    return locale.some((key) => {
      if (phones.hasOwnProperty(key)) {
        const phone = phones[key];
        if (phone.test(str)) {
          return true;
        }
      }
      return false;
    });
  } else if (locale in phones) {
    return phones[locale].test(str);
  } else if (locale === 'any') {
    for (const key in phones) {
      if (phones.hasOwnProperty(key)) {
        const phone = phones[key];
        if (phone.test(str)) {
          return true;
        }
      }
    }
    return false;
  }
  throw new Error(`Invalid locale '${locale}'`);
}
<commit_msg>Fix Indonesian phone number regex<commit_after>import assertString from './util/assertString';

/* eslint-disable max-len */
const phones = {
  'ar-AE': /^((\+?971)|0)?5[024568]\d{7}$/,
  'ar-DZ': /^(\+?213|0)(5|6|7)\d{8}$/,
  'ar-EG': /^((\+?20)|0)?1[012]\d{8}$/,
  'ar-IQ': /^(\+?964|0)?7[0-9]\d{8}$/,
  'ar-JO': /^(\+?962|0)?7[789]\d{7}$/,
  'ar-KW': /^(\+?965)[569]\d{7}$/,
  'ar-SA': /^(!?(\+?966)|0)?5\d{8}$/,
  'ar-SY': /^(!?(\+?963)|0)?9\d{8}$/,
  'ar-TN': /^(\+?216)?[2459]\d{7}$/,
  'be-BY': /^(\+?375)?(24|25|29|33|44)\d{7}$/,
  'bg-BG': /^(\+?359|0)?8[789]\d{7}$/,
  'cs-CZ': /^(\+?420)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/,
  'da-DK': /^(\+?45)?\s?\d{2}\s?\d{2}\s?\d{2}\s?\d{2}$/,
  'de-DE': /^(\+?49[ \.\-]?)?([\(]{1}[0-9]{1,6}[\)])?([0-9 \.\-\/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$/,
  'el-GR': /^(\+?30|0)?(69\d{8})$/,
  'en-AU': /^(\+?61|0)4\d{8}$/,
  'en-GB': /^(\+?44|0)7\d{9}$/,
  'en-HK': /^(\+?852\-?)?[456789]\d{3}\-?\d{4}$/,
  'en-IN': /^(\+?91|0)?[6789]\d{9}$/,
  'en-KE': /^(\+?254|0)?[7]\d{8}$/,
  'en-NG': /^(\+?234|0)?[789]\d{9}$/,
  'en-NZ': /^(\+?64|0)2\d{7,9}$/,
  'en-PK': /^((\+92)|(0092))-{0,1}\d{3}-{0,1}\d{7}$|^\d{11}$|^\d{4}-\d{7}$/,
  'en-RW': /^(\+?250|0)?[7]\d{8}$/,
  'en-SG': /^(\+65)?[89]\d{7}$/,
  'en-TZ': /^(\+?255|0)?[67]\d{8}$/,
  'en-UG': /^(\+?256|0)?[7]\d{8}$/,
  'en-US': /^(\+?1)?[2-9]\d{2}[2-9](?!11)\d{6}$/,
  'en-ZA': /^(\+?27|0)\d{9}$/,
  'en-ZM': /^(\+?26)?09[567]\d{7}$/,
  'es-ES': /^(\+?34)?(6\d{1}|7[1234])\d{7}$/,
  'et-EE': /^(\+?372)?\s?(5|8[1-4])\s?([0-9]\s?){6,7}$/,
  'fa-IR': /^(\+?98[\-\s]?|0)9[0-39]\d[\-\s]?\d{3}[\-\s]?\d{4}$/,
  'fi-FI': /^(\+?358|0)\s?(4(0|1|2|4|5|6)?|50)\s?(\d\s?){4,8}\d$/,
  'fo-FO': /^(\+?298)?\s?\d{2}\s?\d{2}\s?\d{2}$/,
  'fr-FR': /^(\+?33|0)[67]\d{8}$/,
  'he-IL': /^(\+972|0)([23489]|5[012345689]|77)[1-9]\d{6}/,
  'hu-HU': /^(\+?36)(20|30|70)\d{7}$/,
  'id-ID': /^(\+?62)(0?8\d\d\d?)(\d{8,12})$/,
  'it-IT': /^(\+?39)?\s?3\d{2} ?\d{6,7}$/,
  'ja-JP': /^(\+?81|0)[789]0[ \-]?[1-9]\d{2}[ \-]?\d{5}$/,
  'kk-KZ': /^(\+?7|8)?7\d{9}$/,
  'kl-GL': /^(\+?299)?\s?\d{2}\s?\d{2}\s?\d{2}$/,
  'ko-KR': /^((\+?82)[ \-]?)?0?1([0|1|6|7|8|9]{1})[ \-]?\d{3,4}[ \-]?\d{4}$/,
  'lt-LT': /^(\+370|8)\d{8}$/,
  'ms-MY': /^(\+?6?01){1}(([145]{1}(\-|\s)?\d{7,8})|([236789]{1}(\s|\-)?\d{7}))$/,
  'nb-NO': /^(\+?47)?[49]\d{7}$/,
  'nl-BE': /^(\+?32|0)4?\d{8}$/,
  'nn-NO': /^(\+?47)?[49]\d{7}$/,
  'pl-PL': /^(\+?48)? ?[5-8]\d ?\d{3} ?\d{2} ?\d{2}$/,
  'pt-BR': /^(\+?55|0)\-?[1-9]{2}\-?[2-9]{1}\d{3,4}\-?\d{4}$/,
  'pt-PT': /^(\+?351)?9[1236]\d{7}$/,
  'ro-RO': /^(\+?4?0)\s?7\d{2}(\/|\s|\.|\-)?\d{3}(\s|\.|\-)?\d{3}$/,
  'ru-RU': /^(\+?7|8)?9\d{9}$/,
  'sk-SK': /^(\+?421)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/,
  'sr-RS': /^(\+3816|06)[- \d]{5,9}$/,
  'sv-SE': /^(\+?46|0)[\s\-]?7[\s\-]?[02369]([\s\-]?\d){7}$/,
  'th-TH': /^(\+66|66|0)\d{9}$/,
  'tr-TR': /^(\+?90|0)?5\d{9}$/,
  'uk-UA': /^(\+?38|8)?0\d{9}$/,
  'vi-VN': /^(\+?84|0)?((1(2([0-9])|6([2-9])|88|99))|(9((?!5)[0-9])))([0-9]{7})$/,
  'zh-CN': /^(\+?0?86\-?)?1[3456789]\d{9}$/,
  'zh-TW': /^(\+?886\-?|0)?9\d{8}$/,
};
/* eslint-enable max-len */

// aliases
phones['en-CA'] = phones['en-US'];
phones['fr-BE'] = phones['nl-BE'];
phones['zh-HK'] = phones['en-HK'];

export default function isMobilePhone(str, locale, options) {
  assertString(str);
  if (options && options.strictMode && !str.startsWith('+')) {
    return false;
  }
  if (Array.isArray(locale)) {
    return locale.some((key) => {
      if (phones.hasOwnProperty(key)) {
        const phone = phones[key];
        if (phone.test(str)) {
          return true;
        }
      }
      return false;
    });
  } else if (locale in phones) {
    return phones[locale].test(str);
  } else if (locale === 'any') {
    for (const key in phones) {
      if (phones.hasOwnProperty(key)) {
        const phone = phones[key];
        if (phone.test(str)) {
          return true;
        }
      }
    }
    return false;
  }
  throw new Error(`Invalid locale '${locale}'`);
}
<|endoftext|>


<commit_before>import assertString from './util/assertString';

/* eslint-disable max-len */
const phones = {
  'ar-DZ': /^(\+?213|0)(5|6|7)\d{8}$/,
  'ar-SY': /^(!?(\+?963)|0)?9\d{8}$/,
  'ar-SA': /^(!?(\+?966)|0)?5\d{8}$/,
  'en-US': /^(\+?1)?[2-9]\d{2}[2-9](?!11)\d{6}$/,
  'cs-CZ': /^(\+?420)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/,
  'de-DE': /^(\+?49[ \.\-])?([\(]{1}[0-9]{1,6}[\)])?([0-9 \.\-\/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$/,
  'da-DK': /^(\+?45)?(\d{8})$/,
  'el-GR': /^(\+?30)?(69\d{8})$/,
  'en-AU': /^(\+?61|0)4\d{8}$/,
  'en-GB': /^(\+?44|0)7\d{9}$/,
  'en-HK': /^(\+?852\-?)?[569]\d{3}\-?\d{4}$/,
  'en-IN': /^(\+?91|0)?[789]\d{9}$/,
  'en-NG': /^(\+?234|0)?[789]\d{9}$/,
  'en-NZ': /^(\+?64|0)2\d{7,9}$/,
  'en-ZA': /^(\+?27|0)\d{9}$/,
  'en-ZM': /^(\+?26)?09[567]\d{7}$/,
  'es-ES': /^(\+?34)?(6\d{1}|7[1234])\d{7}$/,
  'fi-FI': /^(\+?358|0)\s?(4(0|1|2|4|5|6)?|50)\s?(\d\s?){4,8}\d$/,
  'fr-FR': /^(\+?33|0)[67]\d{8}$/,
  'he-IL': /^(\+972|0)([23489]|5[0248]|77)[1-9]\d{6}/,
  'hu-HU': /^(\+?36)(20|30|70)\d{7}$/,
  'id-ID': /^(\+?62|0[1-9])[\s|\d]+$/,
  'it-IT': /^(\+?39)?\s?3\d{2} ?\d{6,7}$/,
  'ja-JP': /^(\+?81|0)\d{1,4}[ \-]?\d{1,4}[ \-]?\d{4}$/,
  'ms-MY': /^(\+?6?01){1}(([145]{1}(\-|\s)?\d{7,8})|([236789]{1}(\s|\-)?\d{7}))$/,
  'nb-NO': /^(\+?47)?[49]\d{7}$/,
  'nl-BE': /^(\+?32|0)4?\d{8}$/,
  'nn-NO': /^(\+?47)?[49]\d{7}$/,
  'pl-PL': /^(\+?48)? ?[5-8]\d ?\d{3} ?\d{2} ?\d{2}$/,
  'pt-BR': /^(\+?55|0)\-?[1-9]{2}\-?[2-9]{1}\d{3,4}\-?\d{4}$/,
  'pt-PT': /^(\+?351)?9[1236]\d{7}$/,
  'ro-RO': /^(\+?4?0)\s?7\d{2}(\/|\s|\.|\-)?\d{3}(\s|\.|\-)?\d{3}$/,
  'en-PK': /^((\+92)|(0092))-{0,1}\d{3}-{0,1}\d{7}$|^\d{11}$|^\d{4}-\d{7}$/,
  'ru-RU': /^(\+?7|8)?9\d{9}$/,
  'sr-RS': /^(\+3816|06)[- \d]{5,9}$/,
  'tr-TR': /^(\+?90|0)?5\d{9}$/,
  'vi-VN': /^(\+?84|0)?((1(2([0-9])|6([2-9])|88|99))|(9((?!5)[0-9])))([0-9]{7})$/,
  'zh-CN': /^(\+?0?86\-?)?1[345789]\d{9}$/,
  'zh-TW': /^(\+?886\-?|0)?9\d{8}$/,
};
/* eslint-enable max-len */

// aliases
phones['en-CA'] = phones['en-US'];
phones['fr-BE'] = phones['nl-BE'];
phones['zh-HK'] = phones['en-HK'];

export default function isMobilePhone(str, locale) {
  assertString(str);
  if (locale in phones) {
    return phones[locale].test(str);
  } else if (locale === 'any') {
    return !!Object.values(phones).find( phone => phone.test(str) );
  }
  return false;
}
<commit_msg>Removed spaces in parenthesis to prevent test error<commit_after>import assertString from './util/assertString';

/* eslint-disable max-len */
const phones = {
  'ar-DZ': /^(\+?213|0)(5|6|7)\d{8}$/,
  'ar-SY': /^(!?(\+?963)|0)?9\d{8}$/,
  'ar-SA': /^(!?(\+?966)|0)?5\d{8}$/,
  'en-US': /^(\+?1)?[2-9]\d{2}[2-9](?!11)\d{6}$/,
  'cs-CZ': /^(\+?420)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/,
  'de-DE': /^(\+?49[ \.\-])?([\(]{1}[0-9]{1,6}[\)])?([0-9 \.\-\/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$/,
  'da-DK': /^(\+?45)?(\d{8})$/,
  'el-GR': /^(\+?30)?(69\d{8})$/,
  'en-AU': /^(\+?61|0)4\d{8}$/,
  'en-GB': /^(\+?44|0)7\d{9}$/,
  'en-HK': /^(\+?852\-?)?[569]\d{3}\-?\d{4}$/,
  'en-IN': /^(\+?91|0)?[789]\d{9}$/,
  'en-NG': /^(\+?234|0)?[789]\d{9}$/,
  'en-NZ': /^(\+?64|0)2\d{7,9}$/,
  'en-ZA': /^(\+?27|0)\d{9}$/,
  'en-ZM': /^(\+?26)?09[567]\d{7}$/,
  'es-ES': /^(\+?34)?(6\d{1}|7[1234])\d{7}$/,
  'fi-FI': /^(\+?358|0)\s?(4(0|1|2|4|5|6)?|50)\s?(\d\s?){4,8}\d$/,
  'fr-FR': /^(\+?33|0)[67]\d{8}$/,
  'he-IL': /^(\+972|0)([23489]|5[0248]|77)[1-9]\d{6}/,
  'hu-HU': /^(\+?36)(20|30|70)\d{7}$/,
  'id-ID': /^(\+?62|0[1-9])[\s|\d]+$/,
  'it-IT': /^(\+?39)?\s?3\d{2} ?\d{6,7}$/,
  'ja-JP': /^(\+?81|0)\d{1,4}[ \-]?\d{1,4}[ \-]?\d{4}$/,
  'ms-MY': /^(\+?6?01){1}(([145]{1}(\-|\s)?\d{7,8})|([236789]{1}(\s|\-)?\d{7}))$/,
  'nb-NO': /^(\+?47)?[49]\d{7}$/,
  'nl-BE': /^(\+?32|0)4?\d{8}$/,
  'nn-NO': /^(\+?47)?[49]\d{7}$/,
  'pl-PL': /^(\+?48)? ?[5-8]\d ?\d{3} ?\d{2} ?\d{2}$/,
  'pt-BR': /^(\+?55|0)\-?[1-9]{2}\-?[2-9]{1}\d{3,4}\-?\d{4}$/,
  'pt-PT': /^(\+?351)?9[1236]\d{7}$/,
  'ro-RO': /^(\+?4?0)\s?7\d{2}(\/|\s|\.|\-)?\d{3}(\s|\.|\-)?\d{3}$/,
  'en-PK': /^((\+92)|(0092))-{0,1}\d{3}-{0,1}\d{7}$|^\d{11}$|^\d{4}-\d{7}$/,
  'ru-RU': /^(\+?7|8)?9\d{9}$/,
  'sr-RS': /^(\+3816|06)[- \d]{5,9}$/,
  'tr-TR': /^(\+?90|0)?5\d{9}$/,
  'vi-VN': /^(\+?84|0)?((1(2([0-9])|6([2-9])|88|99))|(9((?!5)[0-9])))([0-9]{7})$/,
  'zh-CN': /^(\+?0?86\-?)?1[345789]\d{9}$/,
  'zh-TW': /^(\+?886\-?|0)?9\d{8}$/,
};
/* eslint-enable max-len */

// aliases
phones['en-CA'] = phones['en-US'];
phones['fr-BE'] = phones['nl-BE'];
phones['zh-HK'] = phones['en-HK'];

export default function isMobilePhone(str, locale) {
  assertString(str);
  if (locale in phones) {
    return phones[locale].test(str);
  } else if (locale === 'any') {
    return !!Object.values(phones).find(phone => phone.test(str));
  }
  return false;
}
<|endoftext|>


<commit_before><commit_msg>fix check for jquery<commit_after><|endoftext|>


<commit_before>require('./build/check-theme')();

const ReplacePlugin = require('replace-bundle-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

// Webpack configs
const devConfig = require('./build/webpack.dev.conf.js');
const prodConfig = require('./build/webpack.prod.conf.js');
// Index templates
const devIndex = './styleguide/dev-index.ejs';
const prodIndex = './styleguide/prod-index.ejs';

let webpConfig;
let indexTemplate;

// Set the correct values for dev vs prod environemnt
if (process.env.NODE_ENV === 'development') {
  delete devConfig.plugins;
  devConfig.plugins = [];
  // for replacing urls that are different between dev and gh-pages
  const rename = new ReplacePlugin([{
    partten: /\/static\//g,
    replacement() {
      return '/';
    },
  }]);
  devConfig.plugins.push(rename);
  webpConfig = devConfig;

  indexTemplate = devIndex;// dev index.html
} else if (process.env.NODE_ENV === 'production') {
  // for replacing urls that are different between dev and gh-pages
  const rename = new ReplacePlugin([{
    partten: /\/static\//g, // requires this misspelling of 'pattern'
    replacement() {
      return '/rei-cedar/static/';
    },
  }]);
  prodConfig.plugins.push(rename);
  const clean = new CleanWebpackPlugin('dist-docs');
  prodConfig.plugins.push(clean);
  webpConfig = prodConfig;

  indexTemplate = prodIndex; // prod index.html
}

module.exports = {
  webpackConfig: webpConfig,
  require: [
    './src/css/main.postcss',
  ],
  ignore: [
    '**/examples/**',
    '**/testing/**', // ignore e2e testing example
  ],
  assetsDir: './static',
  styleguideDir: './dist-docs',
  template: indexTemplate,
  showUsage: true,
  contextDependencies: [
    './src/components',
    './src/compositions',
  ],
  sections: [
    {
      name: 'Introduction',
      content: 'docs/Introduction.md',
    },
    {
      name: 'Documentation',
      sections: [
        {
          name: 'Installation',
          content: 'docs/Installation.md',
        },
        {
          name: 'Configuration',
          content: 'docs/Configuration.md',
        },
      ],
    },
    {
      name: 'Components',
      content: 'docs/Components.md',
      components: 'src/components/**/*.vue',
    },
    {
      name: 'Compositions',
      components: 'src/compositions/**/*.vue',
    },
  ],
};
<commit_msg><commit_after>require('./build/check-theme')();

const ReplacePlugin = require('replace-bundle-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

// Webpack configs
const devConfig = require('./build/webpack.dev.conf.js');
const prodConfig = require('./build/webpack.prod.conf.js');
// Index templates
const devIndex = './styleguide/dev-index.ejs';
const prodIndex = './styleguide/prod-index.ejs';

let webpConfig;
let indexTemplate;

// Set the correct values for dev vs prod environemnt
if (process.env.NODE_ENV === 'development') {
  delete devConfig.plugins;
  devConfig.plugins = [];
  // for replacing urls that are different between dev and gh-pages
  const rename = new ReplacePlugin([{
    partten: /\/static\//g, // requires this misspelling of 'pattern'
    replacement() {
      return '/';
    },
  }]);
  devConfig.plugins.push(rename);
  webpConfig = devConfig;

  indexTemplate = devIndex;// dev index.html
} else if (process.env.NODE_ENV === 'production') {
  // for replacing urls that are different between dev and gh-pages
  const rename = new ReplacePlugin([{
    partten: /\/static\//g, // requires this misspelling of 'pattern'
    replacement() {
      return '/rei-cedar/static/';
    },
  }]);
  prodConfig.plugins.push(rename);
  const clean = new CleanWebpackPlugin('dist-docs');
  prodConfig.plugins.push(clean);
  webpConfig = prodConfig;

  indexTemplate = prodIndex; // prod index.html
}

module.exports = {
  webpackConfig: webpConfig,
  require: [
    './src/css/main.postcss',
  ],
  ignore: [
    '**/examples/**',
    '**/testing/**', // ignore e2e testing example
    '**/node_modules/**',
  ],
  assetsDir: './static',
  styleguideDir: './dist-docs',
  template: indexTemplate,
  showUsage: true,
  contextDependencies: [
    './src/components',
    './src/compositions',
  ],
  sections: [
    {
      name: 'Introduction',
      content: 'docs/Introduction.md',
    },
    {
      name: 'Documentation',
      sections: [
        {
          name: 'Installation',
          content: 'docs/Installation.md',
        },
        {
          name: 'Configuration',
          content: 'docs/Configuration.md',
        },
      ],
    },
    {
      name: 'Components',
      content: 'docs/Components.md',
      components: 'src/components/**/*.vue',
    },
    {
      name: 'Compositions',
      components: 'src/compositions/**/*.vue',
    },
  ],
  // dangerouslyUpdateWebpackConfig(webpackConfig) {
  //   // WARNING: inspect Vue Styleguidist Webpack config before modifying it,
  //   // otherwise you may break Styleguidist
  //   console.log(webpackConfig);
  // },
};
<|endoftext|>


<commit_before>'use strict'

let sendMessage = (action) => {
  let url = `https://itch-internal/${action}`
  let xhr = new window.XMLHttpRequest()
  xhr.open('POST', url)
  xhr.send()
}

window.addEventListener('keydown', (e) => {
  switch (e.keyIdentifier) {
    case 'F11':
      sendMessage('toggle-fullscreen')
      break
    case 'F12':
      if (!e.shiftKey) return
      sendMessage('open-devtools')
      break
  }
})

window.addEventListener('DOMContentLoaded', (e) => {
  let gm4 = document.querySelectorAll('div.gm4html5_div_class')
  let emscripten = document.querySelectorAll('canvas.emscripten')
  if (gm4.length + emscripten.length === 0) {
    console.log(`Didn't detect emscripten or gm4, not trying to fit to window`)
    return
  }

  let canvases = document.getElementsByTagName('canvas')
  if (canvases.length !== 1) {
    console.log(`Didn't find exactly 1 canvas, not trying to fit to window`)
  }
  let canvas = canvases[0]

  let refit_canvas = function () {
    if (window.innerHeight > 0) {
      document.body.style.overflow = 'hidden'
      canvas.style.position = 'fixed'
      canvas.style.margin = '0'

      let ratio = canvas.width / canvas.height
      canvas.style.height = window.innerHeight + 'px'

      let stretchWidth = window.innerHeight * ratio
      let horizontalMargin = (window.innerWidth - stretchWidth) / 2

      canvas.style.top = '0'
      canvas.style.left = horizontalMargin.toFixed() + 'px'
    }
  }
  window.onresize = refit_canvas
  refit_canvas()
})
<commit_msg>bypass some unity webgl wrappers<commit_after>'use strict'

let sendMessage = (action) => {
  let url = `https://itch-internal/${action}`
  let xhr = new window.XMLHttpRequest()
  xhr.open('POST', url)
  xhr.send()
}

window.addEventListener('keydown', (e) => {
  switch (e.keyIdentifier) {
    case 'F11':
      sendMessage('toggle-fullscreen')
      break
    case 'F12':
      if (!e.shiftKey) return
      sendMessage('open-devtools')
      break
  }
})

window.addEventListener('DOMContentLoaded', (e) => {
  let gm4 = document.querySelectorAll('div.gm4html5_div_class')
  let emscripten = document.querySelectorAll('canvas.emscripten')
  if (gm4.length + emscripten.length === 0) {
    console.log(`Didn't detect emscripten or gm4, not trying to fit to window`)
    return
  }

  let canvases = document.getElementsByTagName('canvas')
  if (canvases.length !== 1) {
    console.log(`Didn't find exactly 1 canvas, not trying to fit to window`)
  }
  let canvas = canvases[0]

  let refit_canvas = function () {
    if (window.innerHeight > 0) {
      document.body.style.overflow = 'hidden'
      canvas.style.position = 'fixed'
      canvas.style.margin = '0'

      let ratio = canvas.width / canvas.height
      canvas.style.height = window.innerHeight + 'px'

      let stretchWidth = window.innerHeight * ratio
      let horizontalMargin = (window.innerWidth - stretchWidth) / 2

      canvas.style.top = '0'
      canvas.style.left = horizontalMargin.toFixed() + 'px'

      let parent = canvas.parentNode
      while (parent && parent.style) {
        parent.style.transform = 'none'
        parent.style['webkit-transform'] = 'none'
        parent = parent.parentNode
      }
    }
  }
  window.onresize = refit_canvas
  refit_canvas()
})
<|endoftext|>