<commit_before>/*!
 * jQuery UI Controlgroup @VERSION
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Controlgroup
//>>group: Widgets
//>>description: Visually groups form control widgets
//>>docs: http://api.jqueryui.com/controlgroup/
//>>demos: http://jqueryui.com/controlgroup/
//>>css.structure: ../themes/base/core.css, ../themes/base/controlgroup.css
//>>css.theme: ../themes/base/theme.css

( function( factory ) {
	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [
			"jquery",
			"../widget"
		], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
}( function( $ ) {

return $.widget( "ui.controlgroup", {
	version: "@VERSION",
	defaultElement: "
",
	options: {
		direction: "horizontal",
		disabled: null,
		onlyVisible: true,
		items: {
			"button": "input[type=button], input[type=submit], input[type=reset], button, a",
			"controlgroupLabel": ".ui-controlgroup-label",
			"checkboxradio": "input[type='checkbox'], input[type='radio']",
			"selectmenu": "select",
			"spinner": ".ui-spinner-input"
		}
	},

	_create: function() {
		this._enhance();
	},

	// To support the enhanced option in jQuery Mobile, we isolate DOM manipulation
	_enhance: function() {
		this.element.attr( "role", "toolbar" );
		this.refresh();
	},

	_destroy: function() {
		this._callChildMethod( "destroy" );
		this.childWidgets.removeData( "ui-controlgroup-data" );
		this.element.removeAttr( "role" );
	},

	_initWidgets: function() {
		var that = this,
			childWidgets = [];

		// First we iterate over each of the items options
		$.each( this.options.items, function( widget, selector ) {
			var widgets, labels,
				options = {};

			// We assume everything is in the middle to start because we can't determine
			// first / last elements until all enhancments are done.
			if ( that[ "_" + widget + "Options" ] ) {
				options = that[ "_" + widget + "Options" ]( "middle" );
			}

			// Make sure the widget actually exists and has a selector set
			if ( $.fn[ widget ] && selector ) {

				// Find instances of this widget inside controlgroup init them
				widgets = that.element.find( selector )[ widget ]( options );

				widgets.each( function() {
					var element = $( this );

					// Store an instance of the controlgroup to be able to refrence
					var widgetElement = element[ widget ]( "widget" );

					widgetElement.data(
						"ui-controlgroup-data",
						element.data( "ui-" + widget.charAt( 0 ).toUpperCase() + widget.slice( 1 ) )
					);

					childWidgets.push( widgetElement[ 0 ] );
				} );
			} else if ( selector && widget === "controlgroupLabel" ) {
				labels = that.element.find( selector );
				labels.each( function() {
					$( this ).contents().wrapAll( "" );
				} );
				that._addClass( labels, null, "ui-widget ui-widget-content ui-state-default" );
				childWidgets = childWidgets.concat( labels.get() );
			}
		} );

		this.childWidgets = $( $.unique( childWidgets ) );
		this._addClass( this.childWidgets, "ui-controlgroup-item" );
	},

	_callChildMethod: function( method ) {
		this.childWidgets.each( function() {
			var element = $( this ),
				data = element.data( "ui-controlgroup-data" );
			if ( data && data[ method ] ) {
				data[ method ]();
			}
		} );
	},

	_updateCornerClass: function( element, position ) {
		var remove = "ui-corner-top ui-corner-bottom ui-corner-left ui-corner-right";
		var add = this._buildSimpleOptions( position, "label" ).classes.label;

		this._removeClass( element, null, remove );
		this._addClass( element, null, add );
	},

	_buildSimpleOptions: function( position, key ) {
		var direction = this.options.direction === "vertical";
		var result = {
			classes: {}
		};
		result.classes[ key ] = {
			"middle": null,
			"first": "ui-corner-" + ( direction ? "top" : "left" ),
			"last": "ui-corner-" + ( direction ? "bottom" : "right" )
		}[ position ];

		return result;
	},

	_spinnerOptions: function( position ) {
		var options = this._buildSimpleOptions( position, "ui-spinner" );

		options.classes[ "ui-spinner-up" ] = "";
		options.classes[ "ui-spinner-down" ] = "";

		return options;
	},

	_buttonOptions: function( position ) {
		return this._buildSimpleOptions( position, "ui-button" );
	},

	_checkboxradioOptions: function( position ) {
		return this._buildSimpleOptions( position, "ui-checkboxradio-label" );
	},

	_selectmenuOptions: function( position ) {
		var direction = this.options.direction === "vertical";
		return {
			width: direction ? "auto" : false,
			classes: {
				middle: {
					"ui-selectmenu-button-open": null,
					"ui-selectmenu-button-closed": null
				},
				first: {
					"ui-selectmenu-button-open": "ui-corner-" + ( direction ? "top" : "tl" ),
					"ui-selectmenu-button-closed": "ui-corner-" + ( direction ? "top" : "left" )
				},
				last: {
					"ui-selectmenu-button-open": direction ? null : "ui-corner-tr",
					"ui-selectmenu-button-closed": "ui-corner-" + ( direction ? "bottom" : "right" )
				}

			}[ position ]
		};
	},

	_setOption: function( key, value ) {
		if ( key === "direction" ) {
			this._removeClass( "ui-controlgroup-" + this.options.direction );
		}

		this._super( key, value );
		if ( key === "disabled" ) {
			this._callChildMethod( value ? "disable" : "enable" );
			return;
		}

		this.refresh();
	},

	refresh: function() {
		var children,
			that = this;

		this._addClass( "ui-controlgroup ui-controlgroup-" + this.options.direction );

		if ( this.options.direction === "horizontal" ) {
			this._addClass( null, "ui-helper-clearfix" );
		}
		this._initWidgets();

		children = this.childWidgets;

		// We filter here because we need to track all childWidgets not just the visible ones
		if ( this.options.onlyVisible ) {
			children = children.filter( ":visible" );
		}

		if ( children.length ) {

			// We do this last because we need to make sure all enhancment is done
			// before determining first and last
			$.each( [ "first", "last" ], function( index, value ) {
				var instance = children[ value ]().data( "ui-controlgroup-data" );

				if ( instance && that[ "_" + instance.widgetName + "Options" ] ) {
					instance.element[ instance.widgetName ](
						that[ "_" + instance.widgetName + "Options" ]( value )
					);
				} else {
					that._updateCornerClass( children[ value ](), value );
				}
			} );

			// Finally call the refresh method on each of the child widgets.
			this._callChildMethod( "refresh" );
		}
	}
} );
} ) );
<commit_msg>Controlgroup: Improve comments and remove a misleading blank line<commit_after>/*!
 * jQuery UI Controlgroup @VERSION
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Controlgroup
//>>group: Widgets
//>>description: Visually groups form control widgets
//>>docs: http://api.jqueryui.com/controlgroup/
//>>demos: http://jqueryui.com/controlgroup/
//>>css.structure: ../themes/base/core.css, ../themes/base/controlgroup.css
//>>css.theme: ../themes/base/theme.css

( function( factory ) {
	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [
			"jquery",
			"../widget"
		], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
}( function( $ ) {

return $.widget( "ui.controlgroup", {
	version: "@VERSION",
	defaultElement: "
",
	options: {
		direction: "horizontal",
		disabled: null,
		onlyVisible: true,
		items: {
			"button": "input[type=button], input[type=submit], input[type=reset], button, a",
			"controlgroupLabel": ".ui-controlgroup-label",
			"checkboxradio": "input[type='checkbox'], input[type='radio']",
			"selectmenu": "select",
			"spinner": ".ui-spinner-input"
		}
	},

	_create: function() {
		this._enhance();
	},

	// To support the enhanced option in jQuery Mobile, we isolate DOM manipulation
	_enhance: function() {
		this.element.attr( "role", "toolbar" );
		this.refresh();
	},

	_destroy: function() {
		this._callChildMethod( "destroy" );
		this.childWidgets.removeData( "ui-controlgroup-data" );
		this.element.removeAttr( "role" );
	},

	_initWidgets: function() {
		var that = this,
			childWidgets = [];

		// First we iterate over each of the items options
		$.each( this.options.items, function( widget, selector ) {
			var widgets, labels,
				options = {};

			// We assume everything is in the middle to start because we can't determine
			// first / last elements until all enhancments are done.
			if ( that[ "_" + widget + "Options" ] ) {
				options = that[ "_" + widget + "Options" ]( "middle" );
			}

			// Make sure the widget actually exists and has a selector set
			if ( $.fn[ widget ] && selector ) {

				// Find instances of this widget inside controlgroup and init them
				widgets = that.element.find( selector )[ widget ]( options );

				widgets.each( function() {
					var element = $( this );

					// Store an instance of the controlgroup to be able to reference it later
					var widgetElement = element[ widget ]( "widget" );
					widgetElement.data(
						"ui-controlgroup-data",
						element.data( "ui-" + widget.charAt( 0 ).toUpperCase() + widget.slice( 1 ) )
					);

					childWidgets.push( widgetElement[ 0 ] );
				} );
			} else if ( selector && widget === "controlgroupLabel" ) {
				labels = that.element.find( selector );
				labels.each( function() {
					$( this ).contents().wrapAll( "" );
				} );
				that._addClass( labels, null, "ui-widget ui-widget-content ui-state-default" );
				childWidgets = childWidgets.concat( labels.get() );
			}
		} );

		this.childWidgets = $( $.unique( childWidgets ) );
		this._addClass( this.childWidgets, "ui-controlgroup-item" );
	},

	_callChildMethod: function( method ) {
		this.childWidgets.each( function() {
			var element = $( this ),
				data = element.data( "ui-controlgroup-data" );
			if ( data && data[ method ] ) {
				data[ method ]();
			}
		} );
	},

	_updateCornerClass: function( element, position ) {
		var remove = "ui-corner-top ui-corner-bottom ui-corner-left ui-corner-right";
		var add = this._buildSimpleOptions( position, "label" ).classes.label;

		this._removeClass( element, null, remove );
		this._addClass( element, null, add );
	},

	_buildSimpleOptions: function( position, key ) {
		var direction = this.options.direction === "vertical";
		var result = {
			classes: {}
		};
		result.classes[ key ] = {
			"middle": null,
			"first": "ui-corner-" + ( direction ? "top" : "left" ),
			"last": "ui-corner-" + ( direction ? "bottom" : "right" )
		}[ position ];

		return result;
	},

	_spinnerOptions: function( position ) {
		var options = this._buildSimpleOptions( position, "ui-spinner" );

		options.classes[ "ui-spinner-up" ] = "";
		options.classes[ "ui-spinner-down" ] = "";

		return options;
	},

	_buttonOptions: function( position ) {
		return this._buildSimpleOptions( position, "ui-button" );
	},

	_checkboxradioOptions: function( position ) {
		return this._buildSimpleOptions( position, "ui-checkboxradio-label" );
	},

	_selectmenuOptions: function( position ) {
		var direction = this.options.direction === "vertical";
		return {
			width: direction ? "auto" : false,
			classes: {
				middle: {
					"ui-selectmenu-button-open": null,
					"ui-selectmenu-button-closed": null
				},
				first: {
					"ui-selectmenu-button-open": "ui-corner-" + ( direction ? "top" : "tl" ),
					"ui-selectmenu-button-closed": "ui-corner-" + ( direction ? "top" : "left" )
				},
				last: {
					"ui-selectmenu-button-open": direction ? null : "ui-corner-tr",
					"ui-selectmenu-button-closed": "ui-corner-" + ( direction ? "bottom" : "right" )
				}

			}[ position ]
		};
	},

	_setOption: function( key, value ) {
		if ( key === "direction" ) {
			this._removeClass( "ui-controlgroup-" + this.options.direction );
		}

		this._super( key, value );
		if ( key === "disabled" ) {
			this._callChildMethod( value ? "disable" : "enable" );
			return;
		}

		this.refresh();
	},

	refresh: function() {
		var children,
			that = this;

		this._addClass( "ui-controlgroup ui-controlgroup-" + this.options.direction );

		if ( this.options.direction === "horizontal" ) {
			this._addClass( null, "ui-helper-clearfix" );
		}
		this._initWidgets();

		children = this.childWidgets;

		// We filter here because we need to track all childWidgets not just the visible ones
		if ( this.options.onlyVisible ) {
			children = children.filter( ":visible" );
		}

		if ( children.length ) {

			// We do this last because we need to make sure all enhancment is done
			// before determining first and last
			$.each( [ "first", "last" ], function( index, value ) {
				var instance = children[ value ]().data( "ui-controlgroup-data" );

				if ( instance && that[ "_" + instance.widgetName + "Options" ] ) {
					instance.element[ instance.widgetName ](
						that[ "_" + instance.widgetName + "Options" ]( value )
					);
				} else {
					that._updateCornerClass( children[ value ](), value );
				}
			} );

			// Finally call the refresh method on each of the child widgets.
			this._callChildMethod( "refresh" );
		}
	}
} );
} ) );
<|endoftext|>


<commit_before>exports.createPlatformModule = function(platform) {
	if (!platform.sendMessage) {
		platform.sendMessage = function(message, thread) {
			throw "What kind of shit platform is this that doesn't even support sending messages?";
		};
	}

	if (!platform.sendUrl) {
		platform.sendUrl = function(url, thread) {
			platform.sendMessage(url, thread); // fallback to sending a message
		};
	}

	if (!platform.sendImage) {
		platform.sendImage = function(type, image, description, thread) {
			switch(type) {
				case "url": // fallback to sending a url
					platform.sendMessage(description, thread);
					platform.sendUrl(image, thread);
					break;
				case "file": // fallback to sending a file
					platform.sendFile(image, thread);
					break;
				default: // fallback to sending a message
					platform.sendMessage(description, thread);
					platform.sendMessage("I also have something to send you but cant seem to do so...", thread);
					break;
			}
		};
	}

	if (!platform.sendFile) {
		platform.sendFile = function(type, file, description, thread) {
			platform.sendMessage(description, thread);
			switch(type) {
				case "url": // fallback to sending a url
					platform.sendUrl(file, thread);
					break;
				case "file": // fallback to sending a message
					platform.sendMessage("I have a file to send you but cant seem to do so...", thread);
					break;
				default: // fallback to sending a message
					platform.sendMessage("I have something to send you but cant seem to do so...", thread);
					break;
			}
		}
	}

	if (!platform.sendTyping) {
		platform.sendTyping = function(thread) {
			//TODO fix me
			platform.sendMessage("Working on it...", thread); // fallback to sending a message
		}
	}

	if (!platform.setTitle) {
		platform.setTitle = function(title, thread) { // fallback to sending a message
			platform.sendMessage("If I could set the title of this chat I would set it to \"" + title + "\"", thread);
		}
	}

	if (!platform.commandPrefix) {
		if (platform.config && platform.config.commandPrefix) {
			platform.commandPrefix = platform.config.commandPrefix;
		}
		else {
			platform.commandPrefix = '/';
		}
	}

	if (!platform.sendPrivateMessage) {
		platform.sendPrivateMessage = function(message, thread, senderId) {
			platform.sendMessage(message, thread);
		}
	}

	return platform;
};

exports.createEvent = function(thread, senderId, senderName, message) {
	return {
		thread_id: thread,
		sender_id: senderId,
		sender_name: senderName,
		body: message
	};
};
<commit_msg>use single quotes<commit_after>exports.createPlatformModule = function(platform) {
	if (!platform.sendMessage) {
		platform.sendMessage = function(message, thread) {
			throw 'What kind of shit platform is this that doesn\'t even support sending messages?';
		};
	}

	if (!platform.sendUrl) {
		platform.sendUrl = function(url, thread) {
			platform.sendMessage(url, thread); // fallback to sending a message
		};
	}

	if (!platform.sendImage) {
		platform.sendImage = function(type, image, description, thread) {
			switch(type) {
				case 'url': // fallback to sending a url
					platform.sendMessage(description, thread);
					platform.sendUrl(image, thread);
					break;
				case 'file': // fallback to sending a file
					platform.sendFile(image, thread);
					break;
				default: // fallback to sending a message
					platform.sendMessage(description, thread);
					platform.sendMessage('I also have something to send you but cant seem to do so...', thread);
					break;
			}
		};
	}

	if (!platform.sendFile) {
		platform.sendFile = function(type, file, description, thread) {
			platform.sendMessage(description, thread);
			switch(type) {
				case 'url': // fallback to sending a url
					platform.sendUrl(file, thread);
					break;
				case 'file': // fallback to sending a message
					platform.sendMessage('I have a file to send you but cant seem to do so...', thread);
					break;
				default: // fallback to sending a message
					platform.sendMessage('I have something to send you but cant seem to do so...', thread);
					break;
			}
		}
	}

	if (!platform.sendTyping) {
		platform.sendTyping = function(thread) {
			//TODO fix me
			platform.sendMessage('Working on it...', thread); // fallback to sending a message
		}
	}

	if (!platform.setTitle) {
		platform.setTitle = function(title, thread) { // fallback to sending a message
			platform.sendMessage('If I could set the title of this chat I would set it to "' + title + '"', thread);
		}
	}

	if (!platform.commandPrefix) {
		if (platform.config && platform.config.commandPrefix) {
			platform.commandPrefix = platform.config.commandPrefix;
		}
		else {
			platform.commandPrefix = '/';
		}
	}

	if (!platform.sendPrivateMessage) {
		platform.sendPrivateMessage = function(message, thread, senderId) {
			platform.sendMessage(message, thread);
		}
	}

	return platform;
};

exports.createEvent = function(thread, senderId, senderName, message) {
	return {
		thread_id: thread,
		sender_id: senderId,
		sender_name: senderName,
		body: message
	};
};
<|endoftext|>


<commit_before>updateTable();

function getParameterByName(name) {
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
        results = regex.exec(location.search);
    return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

function updateTable()
{
    package_id = getParameterByName("package_id")
    req = {"package_id":package_id};
    $.post("/api/cli", {
        command:"server.service_list " + JSON.stringify(req)
        }, function(data){
            if(~data.indexOf('unknown command'))
            {
                alert('Error: unknown command service_list');
                return;
            }
            data = JSON.parse(data)['services'];
            result = "";

            $("#serviceList tbody").empty();
            for(var service in data)
            {
                result = '
' + data[service]['name'] + '	' + data[service]['package_id'] + '	' + data[service]['cluster'] + '	' + data[service]['service_url'] + '	' + data[service]['status'] + '	' + data[service]['error'] + '
';
                $("#serviceList tbody").append(result);
            }
            window.setTimeout(function () {
                updateTable();
            }, 5000);
        }
    )
    .fail(function() {
        alert( "Error: lost connection to the server" );
        return;
    });
}

function undeploy(service_name) {
    req = {"service_name":service_name};
    $.post("/api/pack/undeploy", {
        command:"server.undeploy " + JSON.stringify(req)
        }, function(data){
            if(data == 'ERR_OK')
            {
                $('#'+service_name).remove();
            }
        }
    )
    .fail(function() {
        alert( "Error: lost connection to the server" );
        return;
    });
}
<commit_msg>fix a bug when undeploy service<commit_after>updateTable();

function getParameterByName(name) {
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
        results = regex.exec(location.search);
    return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

function updateTable()
{
    package_id = getParameterByName("package_id")
    req = {"package_id":package_id};
    $.post("/api/cli", {
        command:"server.service_list " + JSON.stringify(req)
        }, function(data){
            if(~data.indexOf('unknown command'))
            {
                alert('Error: unknown command service_list');
                return;
            }
            data = JSON.parse(data)['services'];
            result = "";

            $("#serviceList tbody").empty();
            for(var service in data)
            {
                result = '
' + data[service]['name'] + '	' + data[service]['package_id'] + '	' + data[service]['cluster'] + '	' + data[service]['service_url'] + '	' + data[service]['status'] + '	' + data[service]['error'] + '
';
                $("#serviceList tbody").append(result);
            }
            window.setTimeout(function () {
                updateTable();
            }, 5000);
        }
    )
    .fail(function() {
        alert( "Error: lost connection to the server" );
        return;
    });
}

function undeploy(service_name) {
    req = {"service_name":service_name};
    $.post("/api/cli", {
        command:"server.undeploy " + JSON.stringify(req)
        }, function(data){
            err = JSON.parse(data)['error'];
            if(err == 'ERR_OK')
            {
                $('#'+service_name).remove();
            }
        }
    )
    .fail(function() {
        alert( "Error: lost connection to the server" );
        return;
    });
}
<|endoftext|>


<commit_before>// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt

cur_frm.email_field = "email_id";
frappe.ui.form.on("Contact", {
	refresh: function(frm) {
		if(frm.doc.__islocal) {
			var last_route = frappe.route_history.slice(-2, -1)[0];
			if(frappe.dynamic_link && frappe.dynamic_link.doc
					&& frappe.dynamic_link.doc.name==last_route[2]) {
				frm.add_child('links', {
					link_doctype: frappe.dynamic_link.doctype,
					link_name: frappe.dynamic_link.doc[frappe.dynamic_link.fieldname]
				});
			}
		}

		if(!frm.doc.user && !frm.is_new() && frm.perm[0].write) {
			frm.add_custom_button(__("Invite as User"), function() {
				frappe.call({
					method: "erpnext.utilities.doctype.contact.contact.invite_user",
					args: {
						contact: frm.doc.name
					},
					callback: function(r) {
						frm.set_value("user", r.message);
					}
				});
			});
		}
		frm.set_query('link_doctype', "links", function() {
			return {
				query: "frappe.geo.address_and_contact.filter_dynamic_link_doctypes",
				filters: {
					fieldtype: "HTML",
					fieldname: "contact_html",
				}
			}
		});
	},
	validate: function(frm) {
		// clear linked customer / supplier / sales partner on saving...
		if(frm.doc.links) {
			frm.doc.links.forEach(function(d) {
				frappe.model.remove_from_locals(d.link_doctype, d.link_name);
			});
		}
	}
});

frappe.ui.form.on("Dynamic Link", {
	link_name:function(frm, cdt, cdn){
		var child = locals[cdt][cdn];
		if(child.link_name) {
			frappe.model.with_doctype(child.link_doctype, function () {
				var title_field = frappe.get_meta(child.link_doctype).title_field || "name"
				frappe.model.get_value(child.link_doctype, child.link_name, title_field, function (r) {
					frappe.model.set_value(cdt, cdn, "link_title", r[title_field])
				})
			})
		}
	}
})
<commit_msg>Update contact.js (#3286)<commit_after>// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt

cur_frm.email_field = "email_id";
frappe.ui.form.on("Contact", {
	refresh: function(frm) {
		if(frm.doc.__islocal) {
			var last_route = frappe.route_history.slice(-2, -1)[0];
			if(frappe.dynamic_link && frappe.dynamic_link.doc
					&& frappe.dynamic_link.doc.name==last_route[2]) {
				frm.add_child('links', {
					link_doctype: frappe.dynamic_link.doctype,
					link_name: frappe.dynamic_link.doc[frappe.dynamic_link.fieldname]
				});
			}
		}

		if(!frm.doc.user && !frm.is_new() && frm.perm[0].write) {
			frm.add_custom_button(__("Invite as User"), function() {
				frappe.call({
					method: "frappe.email.doctype.contact.contact.invite_user",
					args: {
						contact: frm.doc.name
					},
					callback: function(r) {
						frm.set_value("user", r.message);
					}
				});
			});
		}
		frm.set_query('link_doctype', "links", function() {
			return {
				query: "frappe.geo.address_and_contact.filter_dynamic_link_doctypes",
				filters: {
					fieldtype: "HTML",
					fieldname: "contact_html",
				}
			}
		});
	},
	validate: function(frm) {
		// clear linked customer / supplier / sales partner on saving...
		if(frm.doc.links) {
			frm.doc.links.forEach(function(d) {
				frappe.model.remove_from_locals(d.link_doctype, d.link_name);
			});
		}
	}
});

frappe.ui.form.on("Dynamic Link", {
	link_name:function(frm, cdt, cdn){
		var child = locals[cdt][cdn];
		if(child.link_name) {
			frappe.model.with_doctype(child.link_doctype, function () {
				var title_field = frappe.get_meta(child.link_doctype).title_field || "name"
				frappe.model.get_value(child.link_doctype, child.link_name, title_field, function (r) {
					frappe.model.set_value(cdt, cdn, "link_title", r[title_field])
				})
			})
		}
	}
})
<|endoftext|>


<commit_before>const should = require('should');
const supertest = require('supertest');
const sinon = require('sinon');
const testUtils = require('../../../utils');
const localUtils = require('./utils');
const config = require('../../../../../core/server/config');
const labs = require('../../../../../core/server/services/labs');
const ghost = testUtils.startGhost;
const sandbox = sinon.sandbox.create();
let request;

describe('Subscribers API', function () {
    let accesstoken = '', ghostServer;

    before(function () {
        sandbox.stub(labs, 'isSet').withArgs('subscribers').returns(true);
    });

    after(function () {
        sandbox.restore();
    });

    before(function () {
        return ghost()
            .then(function (_ghostServer) {
                ghostServer = _ghostServer;
                request = supertest.agent(config.get('url'));
            })
            .then(function () {
                return localUtils.doAuth(request, 'subscriber');
            })
            .then(function (token) {
                accesstoken = token;
            });
    });

    it('browse', function () {
        return request
            .get(localUtils.API.getApiQuery('subscribers/'))
            .set('Authorization', 'Bearer ' + accesstoken)
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200)
            .then((res) => {
                should.not.exist(res.headers['x-cache-invalidate']);
                const jsonResponse = res.body;
                should.exist(jsonResponse);
                should.exist(jsonResponse.subscribers);
                jsonResponse.subscribers.should.have.length(1);
                testUtils.API.checkResponse(jsonResponse.subscribers[0], 'subscriber');

                testUtils.API.isISO8601(jsonResponse.subscribers[0].created_at).should.be.true();
                jsonResponse.subscribers[0].created_at.should.be.an.instanceof(String);

                jsonResponse.meta.pagination.should.have.property('page', 1);
                jsonResponse.meta.pagination.should.have.property('limit', 15);
                jsonResponse.meta.pagination.should.have.property('pages', 1);
                jsonResponse.meta.pagination.should.have.property('total', 1);
                jsonResponse.meta.pagination.should.have.property('next', null);
                jsonResponse.meta.pagination.should.have.property('prev', null);
            });
    });

    it('read', function () {
        return request
            .get(localUtils.API.getApiQuery(`subscribers/${testUtils.DataGenerator.Content.subscribers[0].id}/`))
            .set('Authorization', 'Bearer ' + accesstoken)
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200)
            .then((res) => {
                should.not.exist(res.headers['x-cache-invalidate']);
                const jsonResponse = res.body;
                should.exist(jsonResponse);
                should.exist(jsonResponse.subscribers);
                jsonResponse.subscribers.should.have.length(1);
                testUtils.API.checkResponse(jsonResponse.subscribers[0], 'subscriber');
            });
    });
});
<commit_msg>Added test coverage for subscriber controller API v0.1<commit_after>const should = require('should');
const supertest = require('supertest');
const sinon = require('sinon');
const path = require('path');
const testUtils = require('../../../utils');
const localUtils = require('./utils');
const config = require('../../../../../core/server/config');
const labs = require('../../../../../core/server/services/labs');

const ghost = testUtils.startGhost;
const sandbox = sinon.sandbox.create();
let request;

describe('Subscribers API', function () {
    let accesstoken = '', ghostServer;

    before(function () {
        sandbox.stub(labs, 'isSet').withArgs('subscribers').returns(true);
    });

    after(function () {
        sandbox.restore();
    });

    before(function () {
        return ghost()
            .then(function (_ghostServer) {
                ghostServer = _ghostServer;
                request = supertest.agent(config.get('url'));
            })
            .then(function () {
                return localUtils.doAuth(request, 'subscriber');
            })
            .then(function (token) {
                accesstoken = token;
            });
    });

    it('browse', function () {
        return request
            .get(localUtils.API.getApiQuery('subscribers/'))
            .set('Authorization', 'Bearer ' + accesstoken)
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200)
            .then((res) => {
                should.not.exist(res.headers['x-cache-invalidate']);
                const jsonResponse = res.body;
                should.exist(jsonResponse);
                should.exist(jsonResponse.subscribers);
                jsonResponse.subscribers.should.have.length(1);
                testUtils.API.checkResponse(jsonResponse.subscribers[0], 'subscriber');

                testUtils.API.isISO8601(jsonResponse.subscribers[0].created_at).should.be.true();
                jsonResponse.subscribers[0].created_at.should.be.an.instanceof(String);

                jsonResponse.meta.pagination.should.have.property('page', 1);
                jsonResponse.meta.pagination.should.have.property('limit', 15);
                jsonResponse.meta.pagination.should.have.property('pages', 1);
                jsonResponse.meta.pagination.should.have.property('total', 1);
                jsonResponse.meta.pagination.should.have.property('next', null);
                jsonResponse.meta.pagination.should.have.property('prev', null);
            });
    });

    it('read', function () {
        return request
            .get(localUtils.API.getApiQuery(`subscribers/${testUtils.DataGenerator.Content.subscribers[0].id}/`))
            .set('Authorization', 'Bearer ' + accesstoken)
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200)
            .then((res) => {
                should.not.exist(res.headers['x-cache-invalidate']);
                const jsonResponse = res.body;
                should.exist(jsonResponse);
                should.exist(jsonResponse.subscribers);
                jsonResponse.subscribers.should.have.length(1);
                testUtils.API.checkResponse(jsonResponse.subscribers[0], 'subscriber');
            });
    });

    it('add', function () {
        const subscriber = {
            name: 'test',
            email: 'subscriberTestAdd@test.com'
        };

        return request
            .post(localUtils.API.getApiQuery(`subscribers/`))
            .send({subscribers: [subscriber]})
            .set('Authorization', 'Bearer ' + accesstoken)
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200)
            .then((res) => {
                should.not.exist(res.headers['x-cache-invalidate']);
                const jsonResponse = res.body;
                should.exist(jsonResponse);
                should.exist(jsonResponse.subscribers);
                jsonResponse.subscribers.should.have.length(1);
                // testUtils.API.checkResponse(jsonResponse.subscribers[0], 'subscriber'); // TODO: modify checked schema
                jsonResponse.subscribers[0].name.should.equal(subscriber.name);
                jsonResponse.subscribers[0].email.should.equal(subscriber.email);
            });
    });

    it('edit by id', function () {
        const subscriberToChange = {
            name: 'changed',
            email: 'subscriber1Changed@test.com'
        };

        const subscriberChanged = {
            name: 'changed',
            email: 'subscriber1Changed@test.com'
        };

        return request
            .post(localUtils.API.getApiQuery(`subscribers/`))
            .send({subscribers: [subscriberToChange]})
            .set('Authorization', 'Bearer ' + accesstoken)
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200)
            .then((res) => {
                should.not.exist(res.headers['x-cache-invalidate']);
                const jsonResponse = res.body;
                should.exist(jsonResponse);
                should.exist(jsonResponse.subscribers);
                jsonResponse.subscribers.should.have.length(1);

                return jsonResponse.subscribers[0];
            })
            .then((newSubscriber) => {
                return request
                    .put(localUtils.API.getApiQuery(`subscribers/${newSubscriber.id}/`))
                    .send({subscribers:[subscriberChanged]})
                    .set('Authorization', 'Bearer ' + accesstoken)
                    .expect('Content-Type', /json/)
                    .expect('Cache-Control', testUtils.cacheRules.private)
                    .expect(200)
                    .then((res) => {
                        should.not.exist(res.headers['x-cache-invalidate']);

                        const jsonResponse = res.body;

                        should.exist(jsonResponse);
                        should.exist(jsonResponse.subscribers);
                        jsonResponse.subscribers.should.have.length(1);
                        testUtils.API.checkResponse(jsonResponse.subscribers[0], 'subscriber');
                        jsonResponse.subscribers[0].name.should.equal(subscriberChanged.name);
                        jsonResponse.subscribers[0].email.should.equal(subscriberChanged.email);
                    });
            });
    });

    it('destroy', function () {
        const subscriber = {
            name: 'test',
            email: 'subscriberTestDestroy@test.com'
        };

        return request
            .post(localUtils.API.getApiQuery(`subscribers/`))
            .send({subscribers: [subscriber]})
            .set('Authorization', 'Bearer ' + accesstoken)
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200)
            .then((res) => {
                should.not.exist(res.headers['x-cache-invalidate']);

                const jsonResponse = res.body;

                should.exist(jsonResponse);
                should.exist(jsonResponse.subscribers);

                return jsonResponse.subscribers[0];
            })
            .then((newSubscriber) => {
                return request
                    .delete(localUtils.API.getApiQuery(`subscribers/${newSubscriber.id}`))
                    .set('Authorization', 'Bearer ' + accesstoken)
                    .expect('Cache-Control', testUtils.cacheRules.private)
                    .expect(204)
                    .then(() => newSubscriber);
            })
            .then((newSubscriber) => {
                return request
                    .get(localUtils.API.getApiQuery(`subscribers/${newSubscriber.id}/`))
                    .set('Authorization', 'Bearer ' + accesstoken)
                    .expect('Content-Type', /json/)
                    .expect('Cache-Control', testUtils.cacheRules.private)
                    .expect(404);
            });
    });

    it('exportCSV', function () {
        return request
            .get(localUtils.API.getApiQuery(`subscribers/csv/`))
            .set('Authorization', 'Bearer ' + accesstoken)
            .expect('Content-Type', /text\/csv/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200)
            .then((res) => {
                should.not.exist(res.headers['x-cache-invalidate']);

                res.text.should.match(/id,email,created_at,deleted_at/);
                res.text.should.match(/subscriber1@test.com/);
            });
    });

    it('importCSV', function () {
        return request
            .post(localUtils.API.getApiQuery(`subscribers/csv/`))
            .attach('subscribersfile', path.join(__dirname, '/../../../utils/fixtures/csv/single-column-with-header.csv'))
            .set('Authorization', 'Bearer ' + accesstoken)
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200)
            .then((res) => {
                should.not.exist(res.headers['x-cache-invalidate']);
                const jsonResponse = res.body;

                should.exist(jsonResponse);
                should.exist(jsonResponse.meta);
                should.exist(jsonResponse.meta.stats);

                jsonResponse.meta.stats.imported.should.equal(2);
                jsonResponse.meta.stats.duplicates.should.equal(0);
                jsonResponse.meta.stats.invalid.should.equal(1);    // TODO: should return 0
            });
    });
});
<|endoftext|>


<commit_before>/**
 * Binds a CodeMirror widget to a <textarea> element.
 */
angular.module('ui.directives').directive('uiCodemirror', ['ui.config', '$parse', function (uiConfig, $parse) {
    uiConfig.codemirror = uiConfig.codemirror || {};
    return {
        require: 'ngModel',
        link: function (scope, elm, attrs, ngModel) {
            // Only works on textareas
            if ( !elm.is('textarea') ) {
                throw Error('ui-codemirror can only be applied to a textarea element');
            }

            var codemirror;
            // This is the method that we use to get the value of the ui-codemirror attribute expression.
            var uiCodemirrorGet = $parse(attrs.uiCodemirror);
            // This method will be called whenever the code mirror widget content changes
            var onChangeHandler =  function (ed) {
                // We only update the model if the value has changed - this helps get around a little problem where $render triggers a change despite already being inside a $apply loop.
                var newValue = ed.getValue();
                if ( newValue !== ngModel.$viewValue ) {
                    ngModel.$setViewValue(newValue);
                    scope.$apply();
                }
            };
            // Create and wire up a new code mirror widget (unwiring a previous one if necessary)
            var updateCodeMirror = function(options) {
                // Merge together the options from the uiConfig and the attribute itself with the onChange event above.
                options = angular.extend({}, options, uiConfig.codemirror);

                // We actually want to run both handlers if the user has provided their own onChange handler.
                var userOnChange = options.onChange;
                if ( userOnChange ) {
                    options.onChange = function(ed) {
                        onChangeHandler(ed);
                        userOnChange(ed);
                    };
                } else {
                    options.onChange = onChangeHandler;
                }

                // If there is a codemirror widget for this element already then we need to unwire if first
                if ( codemirror ) {
                    codemirror.toTextArea();
                }
                // Create the new codemirror widget
                codemirror = CodeMirror.fromTextArea(elm[0], options);
            };

            // Initialize the code mirror widget
            updateCodeMirror(uiCodemirrorGet());

            // Now watch to see if the codemirror attribute gets updated
            scope.$watch(uiCodemirrorGet, updateCodeMirror, true);

            ngModel.$formatters.push(function(value) {
                if(!angular.isString(value)) {
                    return '';
                }
                return value;
            });

            // Override the ngModelController $render method, which is what gets called when the model is updated.
            // This takes care of the synchronizing the codeMirror element with the underlying model, in the case that it is changed by something else.
            ngModel.$render = function() {
                codemirror.setValue(ngModel.$viewValue);
            };
       }
    };
}]);<commit_msg>added little note for #62<commit_after>/**
 * Binds a CodeMirror widget to a <textarea> element.
 */
angular.module('ui.directives').directive('uiCodemirror', ['ui.config', '$parse', function (uiConfig, $parse) {
    uiConfig.codemirror = uiConfig.codemirror || {};
    return {
        require: 'ngModel',
        link: function (scope, elm, attrs, ngModel) {
            // Only works on textareas
            if ( !elm.is('textarea') ) {
                throw Error('ui-codemirror can only be applied to a textarea element');
            }

            var codemirror;
            // This is the method that we use to get the value of the ui-codemirror attribute expression.
            var uiCodemirrorGet = $parse(attrs.uiCodemirror);
            // This method will be called whenever the code mirror widget content changes
            var onChangeHandler =  function (ed) {
                // We only update the model if the value has changed - this helps get around a little problem where $render triggers a change despite already being inside a $apply loop.
                var newValue = ed.getValue();
                if ( newValue !== ngModel.$viewValue ) {
                    ngModel.$setViewValue(newValue);
                    scope.$apply();
                }
            };
            // Create and wire up a new code mirror widget (unwiring a previous one if necessary)
            var updateCodeMirror = function(options) {
                // Merge together the options from the uiConfig and the attribute itself with the onChange event above.
                options = angular.extend({}, options, uiConfig.codemirror);

                // We actually want to run both handlers if the user has provided their own onChange handler.
                var userOnChange = options.onChange;
                if ( userOnChange ) {
                    options.onChange = function(ed) {
                        onChangeHandler(ed);
                        userOnChange(ed);
                    };
                } else {
                    options.onChange = onChangeHandler;
                }

                // If there is a codemirror widget for this element already then we need to unwire if first
                if ( codemirror ) {
                    codemirror.toTextArea();
                }
                // Create the new codemirror widget
                codemirror = CodeMirror.fromTextArea(elm[0], options);
            };

            // Initialize the code mirror widget
            updateCodeMirror(uiCodemirrorGet());

            // Now watch to see if the codemirror attribute gets updated
            scope.$watch(uiCodemirrorGet, updateCodeMirror, true);

            // CodeMirror expects a string, so make sure it gets one.
            // This does not change the model.
            ngModel.$formatters.push(function(value) {
                if(!angular.isString(value)) {
                    return '';
                }
                return value;
            });

            // Override the ngModelController $render method, which is what gets called when the model is updated.
            // This takes care of the synchronizing the codeMirror element with the underlying model, in the case that it is changed by something else.
            ngModel.$render = function() {
                codemirror.setValue(ngModel.$viewValue);
            };
       }
    };
}]);<|endoftext|>


<commit_before>/* @flow */

import type { SideName, Metadata } from './metadata'
import type { Side } from './side' // eslint-disable-line

const Promise = require('bluebird')
const EventEmitter = require('events')
const { dirname } = require('path')

const Ignore = require('./ignore')
const Local = require('./local')
const logger = require('./logger')
const { extractRevNumber, isUpToDate, sameFileIgnoreRev } = require('./metadata')
const Pouch = require('./pouch')
const Remote = require('./remote')
const { HEARTBEAT } = require('./remote/watcher')
const { PendingMap } = require('./utils/pending')
const measureTime = require('./perftools')

const log = logger({
  component: 'Sync'
})

const TRASHING_DELAY = 1000

type MetadataChange = {
  changes: {rev: string}[],
  doc: Metadata,
  id: string,
  seq: number
};

export type SyncMode =
  | "pull"
  | "push"
  | "full";

// Sync listens to PouchDB about the metadata changes, and calls local and
// remote sides to apply the changes on the filesystem and remote CouchDB
// respectively.
class Sync {
  changes: any
  events: EventEmitter
  ignore: Ignore
  local: Local
  pending: PendingMap
  pouch: Pouch
  remote: Remote
  stopped: ?boolean
  moveFrom: ?Metadata
  moveTo: ?string

  static TRASHING_DELAY = TRASHING_DELAY

  diskUsage: () => Promise<*>

  constructor (pouch: Pouch, local: Local, remote: Remote, ignore: Ignore, events: EventEmitter) {
    this.pouch = pouch
    this.local = local
    this.remote = remote
    this.ignore = ignore
    this.events = events
    // $FlowFixMe
    this.local.other = this.remote
    // $FlowFixMe
    this.remote.other = this.local
    this.pending = new PendingMap()
  }

  // Start to synchronize the remote cozy with the local filesystem
  // First, start metadata synchronization in pouch, with the watchers
  // Then, when a stable state is reached, start applying changes from pouch
  //
  // The mode can be:
  // - pull if only changes from the remote cozy are applied to the fs
  // - push if only changes from the fs are applied to the remote cozy
  // - full for the full synchronization of the both sides
  async start (mode: SyncMode): Promise<*> {
    this.stopped = false
    await this.pouch.addAllViewsAsync()
    if (mode !== 'pull') {
      await this.local.start()
    }
    let running = Promise.resolve()
    if (mode !== 'push') {
      const res = this.remote.start()
      running = res.running
      await res.started
    }
    await new Promise(async function (resolve, reject) {
      running.catch((err) => reject(err))
      try {
        while (true) {
          await this.sync()
        }
      } catch (err) {
        reject(err)
      }
    }.bind(this)).catch((err) => {
      this.stop()
      throw err
    })
  }

  // Stop the synchronization
  stop () {
    this.stopped = true
    if (this.changes) {
      this.changes.cancel()
      this.changes = null
    }
    return Promise.all([this.local.stop(), this.remote.stop()])
  }

  // TODO: remove waitForNewChanges to .start while(true)
  async sync (waitForNewChanges:boolean = true): Promise<*> {
    let seq = await this.pouch.getLocalSeqAsync()
    log.trace({seq}, 'Waiting for changes since seq')
    if (waitForNewChanges) await this.waitForNewChanges(seq)
    this.events.emit('sync-start')
    const release = await this.pouch.lock(this)
    try {
      let lastSeq = null
      while (true) {
        if (this.stopped) break
        seq = await this.pouch.getLocalSeqAsync()
        // TODO: if (seq === lastSeq) throw new Error('Infinite loop!')
        if (seq === lastSeq) log.warn({seq}, 'Seq was already synced!')
        else lastSeq = seq

        let change = await this.getNextChange(seq)
        if (change == null) break
        this.events.emit('sync-current', change.seq)
        try {
          await this.apply(change)
          // XXX: apply should call setLocalSeqAsync
        } catch (err) {
          if (!this.stopped) throw err
        }
      }
    } finally {
      release()
      this.events.emit('sync-end')
    }
    log.debug('No more metadata changes for now')
  }

  // We filter with the byPath view to reject design documents
  //
  // Note: it is difficult to pick only one change at a time because pouch can
  // emit several docs in a row, and `limit: 1` seems to be not effective!
  async baseChangeOptions (seq: number) : Object {
    return {
      limit: 1,
      since: seq,
      filter: '_view',
      view: 'byPath',
      returnDocs: false
    }
  }

  async waitForNewChanges (seq: number) {
    const opts = await this.baseChangeOptions(seq)
    opts.live = true
    return new Promise((resolve, reject) => {
      this.changes = this.pouch.db.changes(opts)
        .on('change', () => {
          if (this.changes) {
            this.changes.cancel()
            this.changes = null
            resolve()
          }
        })
        .on('error', err => {
          if (this.changes) {
            this.changes = null
            reject(err)
          }
        })
    })
  }

  async getNextChange (seq: number) : Promise<?MetadataChange> {
    const stopMeasure = measureTime('Sync#getNextChange')
    const opts = await this.baseChangeOptions(seq)
    opts.include_docs = true
    const p = new Promise((resolve, reject) => {
      this.pouch.db.changes(opts)
        .on('change', info => resolve(info))
        .on('error', err => reject(err))
        .on('complete', info => {
          if (info.results == null || info.results.length === 0) {
            resolve(null)
          }
        })
    })
    stopMeasure()
    return p
  }

  // Apply a change to both local and remote
  // At least one side should say it has already this change
  // In some cases, both sides have the change
  async apply (change: MetadataChange): Promise<*> {
    let { doc, seq } = change
    const changeInfo = {
      path: doc.path,
      seq,
      sides: doc.sides,
      moveTo: doc.moveTo,
      moveFrom: this.moveFrom && this.moveFrom._id
    }
    log.debug(changeInfo, 'Applying change...')
    log.trace({change})

    if (this.ignore.isIgnored(doc)) {
      return this.pouch.setLocalSeqAsync(change.seq)
    }

    // FIXME: Acquire lock for as many changes as possible to prevent next huge
    // remote/local batches to acquite it first
    let stopMeasure = () => {}
    try {
      let [side, sideName, rev] = this.selectSide(doc)
      stopMeasure = measureTime('Sync#applyChange:' + sideName)

      if (!side) {
        log.info({path: doc.path}, 'up to date')
        return this.pouch.setLocalSeqAsync(change.seq)
      } else if (sideName === 'remote' && doc.trashed) {
        // File or folder was just deleted locally
        const byItself = await this.trashWithParentOrByItself(doc, side)
        if (!byItself) { return }
      } else {
        await this.applyDoc(doc, side, sideName, rev)
      }

      log.trace(changeInfo, `Applied change on ${sideName} side`)
      await this.pouch.setLocalSeqAsync(change.seq)
      if (!change.doc._deleted) {
        await this.updateRevs(change.doc, sideName)
      }
    } catch (err) {
      await this.handleApplyError(change, err)
    } finally {
      stopMeasure()
    }
  }

  async applyDoc (doc: Metadata, side: Side, sideName:string, rev: number): Promise<*> {
    if (doc.incompatibilities && sideName === 'local' && doc.moveTo == null) {
      const was = doc.moveFrom
      if (was != null && was.incompatibilities == null) {
        // Move compatible -> incompatible
        if (was.childMove == null) {
          log.warn({path: doc.path, oldpath: was.path, incompatibilities: doc.incompatibilities},
            `Trashing ${sideName} ${doc.docType} since new remote one is incompatible`)
          await side.trashAsync(was)
        } else {
          log.debug({path: doc.path, incompatibilities: doc.incompatibilities},
            `incompatible ${doc.docType} should have been trashed with parent`)
        }
      } else {
        log.warn({path: doc.path, incompatibilities: doc.incompatibilities},
          `Not syncing incompatible ${doc.docType}`)
      }
    } else if (doc.docType !== 'file' && doc.docType !== 'folder') {
      throw new Error(`Unknown docType: ${doc.docType}`)
    } else if (doc._deleted && (rev === 0)) {
      // do nothing
    } else if (doc.moveTo != null) {
      log.debug({path: doc.path}, `Ignoring deleted ${doc.docType} metadata as move source`)
    } else if (doc.moveFrom != null) {
      const from = (doc.moveFrom: Metadata)
      // XXX: if (from.md5sum === doc.md5sum) ?
      if (from.incompatibilities) {
        if (doc.docType === 'file') await side.addFileAsync(doc)
        else await side.addFolderAsync(doc)
      } else if (from.childMove) {
        await side.assignNewRev(doc)
      } else {
        if (doc.docType === 'file') await side.moveFileAsync(doc, from)
        else await side.moveFolderAsync(doc, from)
      }
    } else if (doc._deleted) {
      if (doc.docType === 'file') await side.trashAsync(doc)
      else side.deleteFolderAsync(doc)
    } else if (rev === 0) {
      if (doc.docType === 'file') await side.addFileAsync(doc)
      else await side.addFolderAsync(doc)
    } else {
      let old
      try {
        old = await this.pouch.getPreviousRevAsync(doc._id, rev)
      } catch (_) {
        if (doc.docType === 'file') await side.overwriteFileAsync(doc, null)
        else await side.addFolderAsync(doc)
      }

      if (old) {
        if (doc.docType === 'folder') {
          await side.updateFolderAsync(doc, old)
        // $FlowFixMe
        } else if (old.md5sum === doc.md5sum) {
          if (sameFileIgnoreRev(old, doc)) {
            log.trace({path: doc.path}, 'ignoring mtime-only change')
          } else {
            await side.updateFileMetadataAsync(doc, old)
          }
        } else {
          await side.overwriteFileAsync(doc, old)
        }
      }
    }
  }

  // Select which side will apply the change
  // It returns the side, its name, and also the last rev applied by this side
  selectSide (doc: Metadata) {
    let localRev = doc.sides.local || 0
    let remoteRev = doc.sides.remote || 0
    if (localRev > remoteRev) {
      return [this.remote, 'remote', remoteRev]
    } else if (remoteRev > localRev) {
      return [this.local, 'local', localRev]
    } else {
      return []
    }
  }

  // Make the error explicit (offline, local disk full, quota exceeded, etc.)
  // and keep track of the number of retries
  async handleApplyError (change: MetadataChange, err: Error) {
    const {path} = change.doc
    log.error({path, err, change})
    if (err.code === 'ENOSPC') {
      throw new Error('No more disk space')
    } else if (err.status === 413) {
      throw new Error('Cozy is full')
    }
    try {
      await this.diskUsage()
    } catch (err) {
      if (err.status === 400) {
        log.error({err}, 'Client has been revoked')
        throw new Error('Client has been revoked')
      } else if (err.status === 403) {
        log.error({err}, 'Client has wrong permissions (lack disk-usage)')
        throw new Error('Client has wrong permissions (lack disk-usage)')
      } else {
        // The client is offline, wait that it can connect again to the server
        log.warn({path}, 'Client is offline')
        this.events.emit('offline')
        while (true) {
          try {
            await Promise.delay(60000)
            await this.diskUsage()
            this.events.emit('online')
            log.warn({path}, 'Client is online')
            return
          } catch (_) {}
        }
      }
    }
    await this.updateErrors(change)
  }

  // Increment the counter of errors for this document
  async updateErrors (change: MetadataChange): Promise<void> {
    let { doc } = change
    if (!doc.errors) doc.errors = 0
    doc.errors++
    // Don't try more than 3 times for the same operation
    if (doc.errors >= 3) {
      await this.pouch.setLocalSeqAsync(change.seq)
      return
    }
    try {
      // The sync error may be due to the remote cozy being overloaded.
      // So, it's better to wait a bit before trying the next operation.
      // TODO: Wait for some increasing delay before saving errors
      await this.pouch.db.put(doc)
    } catch (err) {
      // If the doc can't be saved, it's because of a new revision.
      // So, we can skip this revision
      log.info(`Ignored ${change.seq}`, err)
      await this.pouch.setLocalSeqAsync(change.seq)
    }
  }

  // Update rev numbers for both local and remote sides
  async updateRevs (doc: Metadata, side: SideName): Promise<*> {
    let rev = extractRevNumber(doc) + 1
    for (let s of ['local', 'remote']) {
      doc.sides[s] = rev
    }
    delete doc.errors
    try {
      await this.pouch.put(doc)
    } catch (err) {
      // Conflicts can happen here, for example if the cozy-stack has generated
      // a thumbnail before apply has finished. In that case, we try to
      // reconciliate the documents.
      if (err && err.status === 409) {
        doc = await this.pouch.db.get(doc._id)
        doc.sides[side] = rev
        await this.pouch.put(doc)
      } else {
        log.warn({path: doc.path, err}, 'Race condition')
      }
    }
  }

  // Trash a file or folder. If a folder was deleted on local, we try to trash
  // only this folder on the remote, not every files and folders inside it, to
  // preserve the tree in the trash.
  async trashWithParentOrByItself (doc: Metadata, side: Side): Promise<boolean> {
    let parentId = dirname(doc._id)
    if (parentId !== '.') {
      let parent = await this.pouch.db.get(parentId)

      if (!parent.trashed) {
        await Promise.delay(TRASHING_DELAY)
        parent = await this.pouch.db.get(parentId)
      }

      if (parent.trashed && !isUpToDate('remote', parent)) {
        log.info(`${doc.path}: will be trashed with parent directory`)
        await this.trashWithParentOrByItself(parent, side)
        // Wait long enough that the remote has fetched one changes feed
        // TODO find a way to trigger the changes feed instead of waiting for it
        await Promise.delay(HEARTBEAT)
        return false
      }
    }

    log.info(`${doc.path}: should be trashed by itself`)
    await side.trashAsync(doc)
    return true
  }
}

module.exports = Sync
<commit_msg>core/sync Remove last traces of sync#moveFrom<commit_after>/* @flow */

import type { SideName, Metadata } from './metadata'
import type { Side } from './side' // eslint-disable-line

const Promise = require('bluebird')
const EventEmitter = require('events')
const { dirname } = require('path')

const Ignore = require('./ignore')
const Local = require('./local')
const logger = require('./logger')
const { extractRevNumber, isUpToDate, sameFileIgnoreRev } = require('./metadata')
const Pouch = require('./pouch')
const Remote = require('./remote')
const { HEARTBEAT } = require('./remote/watcher')
const { PendingMap } = require('./utils/pending')
const measureTime = require('./perftools')

const log = logger({
  component: 'Sync'
})

const TRASHING_DELAY = 1000

type MetadataChange = {
  changes: {rev: string}[],
  doc: Metadata,
  id: string,
  seq: number
};

export type SyncMode =
  | "pull"
  | "push"
  | "full";

// Sync listens to PouchDB about the metadata changes, and calls local and
// remote sides to apply the changes on the filesystem and remote CouchDB
// respectively.
class Sync {
  changes: any
  events: EventEmitter
  ignore: Ignore
  local: Local
  pending: PendingMap
  pouch: Pouch
  remote: Remote
  stopped: ?boolean
  moveTo: ?string

  static TRASHING_DELAY = TRASHING_DELAY

  diskUsage: () => Promise<*>

  constructor (pouch: Pouch, local: Local, remote: Remote, ignore: Ignore, events: EventEmitter) {
    this.pouch = pouch
    this.local = local
    this.remote = remote
    this.ignore = ignore
    this.events = events
    // $FlowFixMe
    this.local.other = this.remote
    // $FlowFixMe
    this.remote.other = this.local
    this.pending = new PendingMap()
  }

  // Start to synchronize the remote cozy with the local filesystem
  // First, start metadata synchronization in pouch, with the watchers
  // Then, when a stable state is reached, start applying changes from pouch
  //
  // The mode can be:
  // - pull if only changes from the remote cozy are applied to the fs
  // - push if only changes from the fs are applied to the remote cozy
  // - full for the full synchronization of the both sides
  async start (mode: SyncMode): Promise<*> {
    this.stopped = false
    await this.pouch.addAllViewsAsync()
    if (mode !== 'pull') {
      await this.local.start()
    }
    let running = Promise.resolve()
    if (mode !== 'push') {
      const res = this.remote.start()
      running = res.running
      await res.started
    }
    await new Promise(async function (resolve, reject) {
      running.catch((err) => reject(err))
      try {
        while (true) {
          await this.sync()
        }
      } catch (err) {
        reject(err)
      }
    }.bind(this)).catch((err) => {
      this.stop()
      throw err
    })
  }

  // Stop the synchronization
  stop () {
    this.stopped = true
    if (this.changes) {
      this.changes.cancel()
      this.changes = null
    }
    return Promise.all([this.local.stop(), this.remote.stop()])
  }

  // TODO: remove waitForNewChanges to .start while(true)
  async sync (waitForNewChanges:boolean = true): Promise<*> {
    let seq = await this.pouch.getLocalSeqAsync()
    log.trace({seq}, 'Waiting for changes since seq')
    if (waitForNewChanges) await this.waitForNewChanges(seq)
    this.events.emit('sync-start')
    const release = await this.pouch.lock(this)
    try {
      let lastSeq = null
      while (true) {
        if (this.stopped) break
        seq = await this.pouch.getLocalSeqAsync()
        // TODO: if (seq === lastSeq) throw new Error('Infinite loop!')
        if (seq === lastSeq) log.warn({seq}, 'Seq was already synced!')
        else lastSeq = seq

        let change = await this.getNextChange(seq)
        if (change == null) break
        this.events.emit('sync-current', change.seq)
        try {
          await this.apply(change)
          // XXX: apply should call setLocalSeqAsync
        } catch (err) {
          if (!this.stopped) throw err
        }
      }
    } finally {
      release()
      this.events.emit('sync-end')
    }
    log.debug('No more metadata changes for now')
  }

  // We filter with the byPath view to reject design documents
  //
  // Note: it is difficult to pick only one change at a time because pouch can
  // emit several docs in a row, and `limit: 1` seems to be not effective!
  async baseChangeOptions (seq: number) : Object {
    return {
      limit: 1,
      since: seq,
      filter: '_view',
      view: 'byPath',
      returnDocs: false
    }
  }

  async waitForNewChanges (seq: number) {
    const opts = await this.baseChangeOptions(seq)
    opts.live = true
    return new Promise((resolve, reject) => {
      this.changes = this.pouch.db.changes(opts)
        .on('change', () => {
          if (this.changes) {
            this.changes.cancel()
            this.changes = null
            resolve()
          }
        })
        .on('error', err => {
          if (this.changes) {
            this.changes = null
            reject(err)
          }
        })
    })
  }

  async getNextChange (seq: number) : Promise<?MetadataChange> {
    const stopMeasure = measureTime('Sync#getNextChange')
    const opts = await this.baseChangeOptions(seq)
    opts.include_docs = true
    const p = new Promise((resolve, reject) => {
      this.pouch.db.changes(opts)
        .on('change', info => resolve(info))
        .on('error', err => reject(err))
        .on('complete', info => {
          if (info.results == null || info.results.length === 0) {
            resolve(null)
          }
        })
    })
    stopMeasure()
    return p
  }

  // Apply a change to both local and remote
  // At least one side should say it has already this change
  // In some cases, both sides have the change
  async apply (change: MetadataChange): Promise<*> {
    let { doc, seq } = change
    const changeInfo = {
      path: doc.path,
      seq,
      sides: doc.sides,
      moveTo: doc.moveTo,
      moveFrom: doc.moveFrom
    }
    log.debug(changeInfo, 'Applying change...')
    log.trace({change})

    if (this.ignore.isIgnored(doc)) {
      return this.pouch.setLocalSeqAsync(change.seq)
    }

    // FIXME: Acquire lock for as many changes as possible to prevent next huge
    // remote/local batches to acquite it first
    let stopMeasure = () => {}
    try {
      let [side, sideName, rev] = this.selectSide(doc)
      stopMeasure = measureTime('Sync#applyChange:' + sideName)

      if (!side) {
        log.info({path: doc.path}, 'up to date')
        return this.pouch.setLocalSeqAsync(change.seq)
      } else if (sideName === 'remote' && doc.trashed) {
        // File or folder was just deleted locally
        const byItself = await this.trashWithParentOrByItself(doc, side)
        if (!byItself) { return }
      } else {
        await this.applyDoc(doc, side, sideName, rev)
      }

      log.trace(changeInfo, `Applied change on ${sideName} side`)
      await this.pouch.setLocalSeqAsync(change.seq)
      if (!change.doc._deleted) {
        await this.updateRevs(change.doc, sideName)
      }
    } catch (err) {
      await this.handleApplyError(change, err)
    } finally {
      stopMeasure()
    }
  }

  async applyDoc (doc: Metadata, side: Side, sideName:string, rev: number): Promise<*> {
    if (doc.incompatibilities && sideName === 'local' && doc.moveTo == null) {
      const was = doc.moveFrom
      if (was != null && was.incompatibilities == null) {
        // Move compatible -> incompatible
        if (was.childMove == null) {
          log.warn({path: doc.path, oldpath: was.path, incompatibilities: doc.incompatibilities},
            `Trashing ${sideName} ${doc.docType} since new remote one is incompatible`)
          await side.trashAsync(was)
        } else {
          log.debug({path: doc.path, incompatibilities: doc.incompatibilities},
            `incompatible ${doc.docType} should have been trashed with parent`)
        }
      } else {
        log.warn({path: doc.path, incompatibilities: doc.incompatibilities},
          `Not syncing incompatible ${doc.docType}`)
      }
    } else if (doc.docType !== 'file' && doc.docType !== 'folder') {
      throw new Error(`Unknown docType: ${doc.docType}`)
    } else if (doc._deleted && (rev === 0)) {
      // do nothing
    } else if (doc.moveTo != null) {
      log.debug({path: doc.path}, `Ignoring deleted ${doc.docType} metadata as move source`)
    } else if (doc.moveFrom != null) {
      const from = (doc.moveFrom: Metadata)
      // XXX: if (from.md5sum === doc.md5sum) ?
      if (from.incompatibilities) {
        if (doc.docType === 'file') await side.addFileAsync(doc)
        else await side.addFolderAsync(doc)
      } else if (from.childMove) {
        await side.assignNewRev(doc)
      } else {
        if (doc.docType === 'file') await side.moveFileAsync(doc, from)
        else await side.moveFolderAsync(doc, from)
      }
    } else if (doc._deleted) {
      if (doc.docType === 'file') await side.trashAsync(doc)
      else side.deleteFolderAsync(doc)
    } else if (rev === 0) {
      if (doc.docType === 'file') await side.addFileAsync(doc)
      else await side.addFolderAsync(doc)
    } else {
      let old
      try {
        old = await this.pouch.getPreviousRevAsync(doc._id, rev)
      } catch (_) {
        if (doc.docType === 'file') await side.overwriteFileAsync(doc, null)
        else await side.addFolderAsync(doc)
      }

      if (old) {
        if (doc.docType === 'folder') {
          await side.updateFolderAsync(doc, old)
        // $FlowFixMe
        } else if (old.md5sum === doc.md5sum) {
          if (sameFileIgnoreRev(old, doc)) {
            log.trace({path: doc.path}, 'ignoring mtime-only change')
          } else {
            await side.updateFileMetadataAsync(doc, old)
          }
        } else {
          await side.overwriteFileAsync(doc, old)
        }
      }
    }
  }

  // Select which side will apply the change
  // It returns the side, its name, and also the last rev applied by this side
  selectSide (doc: Metadata) {
    let localRev = doc.sides.local || 0
    let remoteRev = doc.sides.remote || 0
    if (localRev > remoteRev) {
      return [this.remote, 'remote', remoteRev]
    } else if (remoteRev > localRev) {
      return [this.local, 'local', localRev]
    } else {
      return []
    }
  }

  // Make the error explicit (offline, local disk full, quota exceeded, etc.)
  // and keep track of the number of retries
  async handleApplyError (change: MetadataChange, err: Error) {
    const {path} = change.doc
    log.error({path, err, change})
    if (err.code === 'ENOSPC') {
      throw new Error('No more disk space')
    } else if (err.status === 413) {
      throw new Error('Cozy is full')
    }
    try {
      await this.diskUsage()
    } catch (err) {
      if (err.status === 400) {
        log.error({err}, 'Client has been revoked')
        throw new Error('Client has been revoked')
      } else if (err.status === 403) {
        log.error({err}, 'Client has wrong permissions (lack disk-usage)')
        throw new Error('Client has wrong permissions (lack disk-usage)')
      } else {
        // The client is offline, wait that it can connect again to the server
        log.warn({path}, 'Client is offline')
        this.events.emit('offline')
        while (true) {
          try {
            await Promise.delay(60000)
            await this.diskUsage()
            this.events.emit('online')
            log.warn({path}, 'Client is online')
            return
          } catch (_) {}
        }
      }
    }
    await this.updateErrors(change)
  }

  // Increment the counter of errors for this document
  async updateErrors (change: MetadataChange): Promise<void> {
    let { doc } = change
    if (!doc.errors) doc.errors = 0
    doc.errors++
    // Don't try more than 3 times for the same operation
    if (doc.errors >= 3) {
      await this.pouch.setLocalSeqAsync(change.seq)
      return
    }
    try {
      // The sync error may be due to the remote cozy being overloaded.
      // So, it's better to wait a bit before trying the next operation.
      // TODO: Wait for some increasing delay before saving errors
      await this.pouch.db.put(doc)
    } catch (err) {
      // If the doc can't be saved, it's because of a new revision.
      // So, we can skip this revision
      log.info(`Ignored ${change.seq}`, err)
      await this.pouch.setLocalSeqAsync(change.seq)
    }
  }

  // Update rev numbers for both local and remote sides
  async updateRevs (doc: Metadata, side: SideName): Promise<*> {
    let rev = extractRevNumber(doc) + 1
    for (let s of ['local', 'remote']) {
      doc.sides[s] = rev
    }
    delete doc.errors
    try {
      await this.pouch.put(doc)
    } catch (err) {
      // Conflicts can happen here, for example if the cozy-stack has generated
      // a thumbnail before apply has finished. In that case, we try to
      // reconciliate the documents.
      if (err && err.status === 409) {
        doc = await this.pouch.db.get(doc._id)
        doc.sides[side] = rev
        await this.pouch.put(doc)
      } else {
        log.warn({path: doc.path, err}, 'Race condition')
      }
    }
  }

  // Trash a file or folder. If a folder was deleted on local, we try to trash
  // only this folder on the remote, not every files and folders inside it, to
  // preserve the tree in the trash.
  async trashWithParentOrByItself (doc: Metadata, side: Side): Promise<boolean> {
    let parentId = dirname(doc._id)
    if (parentId !== '.') {
      let parent = await this.pouch.db.get(parentId)

      if (!parent.trashed) {
        await Promise.delay(TRASHING_DELAY)
        parent = await this.pouch.db.get(parentId)
      }

      if (parent.trashed && !isUpToDate('remote', parent)) {
        log.info(`${doc.path}: will be trashed with parent directory`)
        await this.trashWithParentOrByItself(parent, side)
        // Wait long enough that the remote has fetched one changes feed
        // TODO find a way to trigger the changes feed instead of waiting for it
        await Promise.delay(HEARTBEAT)
        return false
      }
    }

    log.info(`${doc.path}: should be trashed by itself`)
    await side.trashAsync(doc)
    return true
  }
}

module.exports = Sync
<|endoftext|>


<commit_before>'use strict';

module.exports = {
    database: 'jdx',
    defaultEntry: function () {
        return {
            parent: [],
            name: [],
            molecule: [],
            nmr: []
        };
    },
    customDesign: {
        version: 1,
        views: {},
        updates: {}
    }
};
<commit_msg>fix import test<commit_after>'use strict';

module.exports = {
    database: 'jdx',
    defaultEntry: function () {
        return {
            parent: [],
            name: [],
            molecule: [],
            nmr: []
        };
    },
    customDesign: {
        version: 1,
        views: {},
        updates: {}
    },
    rights: {
        read: ['anonymous'],
        createGroup: ['anonymous'],
        create: ['anonymous']
    }
};
<|endoftext|>


<commit_before>define([

  'underscore'

], function (

  _

) {
  'use strict';

  /**
   * These method are mixed into `{{#crossLink
   * "Lateralus.Component"}}{{/crossLink}}` and `{{#crossLink
   * "Lateralus.Component.View"}}{{/crossLink}}`.
   * @class Lateralus.mixins
   * @requires http://backbonejs.org/#Events
   */
  var mixins = {};

  /**
   * @param {Object} obj
   * @return {boolean}
   */
  function isLateralus (obj) {
    return obj.toString() === 'lateralus';
  }

  /**
   * Add a subcomponent to a `{{#crossLink "Lateralus"}}{{/crossLink}}` or
   * `{{#crossLink "Lateralus.Component"}}{{/crossLink}}` instance.
   *
   *     var App = Lateralus.beget(function () {
   *       Lateralus.apply(this, arguments);
   *     });
   *
   *     var app = new App(document.getElementById('app'));
   *     var component = app.addComponent(Lateralus.Component);
   *     var subcomponent = component.addComponent(Lateralus.Component);
   * @method addComponent
   * @param {Lateralus.Component} Component A constructor, not an instance.
   * @param {Object} [viewOptions] The `options` object to be passed along to
   * the `Component` parameter's {{#crossLink
   * "Lateralus.Component.View"}}{{/crossLink}} instance.
   * @param {Object} [options] Gets passed to the new {{#crossLink
   * "Lateralus.Component"}}{{/crossLink}} instance.
   * @param {Object} [options.modelAttributes] Any attributes to pre-populate
   * the `{{#crossLink "Lateralus.Component/Model:property"}}{{/crossLink}}`
   * instance with, if there is one.
   * @param {Object} [options.modelOptions] Any parameters to pass to the
   * `{{#crossLink "Lateralus.Component/Model:property"}}{{/crossLink}}`
   * instance, if there is one.
   * @return {Lateralus.Component} The component that was added.
   */
  mixins.addComponent = function (Component, viewOptions, options) {
    options = options || {};

    if (typeof this.component !== 'undefined') {
      return this.component.addComponent.apply(this.component, arguments);
    }

    if (!this.components) {
      /**
       * The subcomponents belonging to this object.  Do not modify this
       * property directly, it is managed by Lateralus.
       * @property components
       * @type {Object(Lateralus.Component)}
       */
      this.components = {};

      /**
       * An internal counter registry of the subcomponents belonging to this
       * object.
       * @property componentCounters
       * @type {Object(number)}
       * @private
       */
      this.componentCounters = {};
    }

    // If thisIsLateralus is false, `this` is a Lateralus.Component instance.
    var thisIsLateralus = isLateralus(this);

    var lateralusReference = thisIsLateralus ? this : this.lateralus;
    var component = new Component(
      lateralusReference
      ,options
      ,viewOptions || {}
      ,thisIsLateralus ? null : this
    );

    if (thisIsLateralus && component.view) {
      this.$el.append(component.view.$el);
    }

    var componentType = component.toString();
    if (this.componentCounters.hasOwnProperty(componentType)) {
      this.componentCounters[componentType]++;
    } else {
      this.componentCounters[componentType] = 0;
    }

    var componentInstanceName =
        componentType + this.componentCounters[componentType];
    this.components[componentInstanceName] = component;

    return component;
  };

  /**
   * Components should never communicate directly with one another in order to
   * maintain a loosely-coupled architecture.  Instead, they should just
   * broadcast general messages with the [`Backbone.Events`
   * API](http://backbonejs.org/#Events).  `emit` facilitates this loose
   * coupling by firing an event that bubbles throughout the app, depending on
   * what calls it:
   *
   * * If this is called by `{{#crossLink "Lateralus"}}{{/crossLink}}`, this
   * just triggers an event on that `{{#crossLink "Lateralus"}}{{/crossLink}}`
   * instance.
   * * If this is called by a `{{#crossLink
   * "Lateralus.Component"}}{{/crossLink}}`, this triggers an event on that
   * `{{#crossLink "Lateralus.Component"}}{{/crossLink}}` as well as the
   * central `{{#crossLink "Lateralus"}}{{/crossLink}}` instance.
   * * If this is called by a `{{#crossLink
   * "Lateralus.Component.View"}}{{/crossLink}}`, this triggers an event on
   * that `{{#crossLink "Lateralus.Component.View"}}{{/crossLink}}`, the
   * `{{#crossLink "Lateralus.Component"}}{{/crossLink}}` to which it belongs,
   * and the central `{{#crossLink "Lateralus"}}{{/crossLink}}` instance.
   *
   * This method has the same method signature as
   * [`Backbone.Events.trigger`](http://backbonejs.org/#Events-trigger).
   * @method emit
   * @param {string} eventName The name of the event.
   * @param {...any} [args] Any arguments to pass along to the listeners.
   */
  mixins.emit = function () {
    var args = _.toArray(arguments);
    this.trigger.apply(this, args);

    if (isLateralus(this)) {
      return;
    }

    if (this.component) {
      this.component.trigger.apply(this.component, args);
    }

    this.lateralus.trigger.apply(this.lateralus, args);
  };

  /**
   * Listen to an event-emitting Object and amplify one of its events across
   * the {{#crossLink "Lateralus"}}{{/crossLink}} application.  Useful for
   * making plain Backbone Objects (i.e., non-Lateralus Objects) communicate
   * important information in a broader way.
   * @method amplify
   * @param {Backbone.Events} emitter The object that `trigger`s events that
   * should be amplified globally across the app.
   * @param {string} eventName The event to amplify globally across the app.
   */
  mixins.amplify = function (emitter, eventName) {
    this.listenTo(emitter, eventName, _.bind(this.emit, this, eventName));
  };

  /**
   * Listen for an event that is triggered on the central {{#crossLink
   * "Lateralus"}}{{/crossLink}} instance and bind a function handler.
   * @method listenFor
   * @param {string} event The name of the event to listen for.
   * @param {Function} callback The function handler to bind.
   */
  mixins.listenFor = function (event, callback) {
    var thisIsLateralus = isLateralus(this);
    if (thisIsLateralus) {
      this.on(event, callback);
    } else {
      this.listenTo(this.lateralus, event, callback);
    }
  };

  var delegateEventSplitter = /^(\S+)\s*(.*)$/;

  /**
   * Bind `{{#crossLink "Lateralus.mixins:lateralusEvents"}}{{/crossLink}}`, if
   * it is defined.
   * @method delegateLateralusEvents
   * @chainable
   */
  mixins.delegateLateralusEvents = function () {

    _.each({
        /**
         * A map of functions or string references to functions that will
         * handle [events](http://backbonejs.org/#Events) dispatched to the
         * central `{{#crossLink "Lateralus"}}{{/crossLink}}` instance.
         *
         *     var ExtendedComponent = Lateralus.Component.extend({
         *       name: 'extended'
         *
         *       ,lateralusEvents: {
         *         anotherComponentChanged: 'onAnotherComponentChanged'
         *
         *         ,anotherComponentDestroyed: function () {
         *           // ...
         *         }
         *       }
         *
         *       ,onAnotherComponentChanged: function () {
         *         // ...
         *       }
         *     });
         * @property lateralusEvents
         * @type {Object|undefined}
         * @default undefined
         */
        lateralusEvents: this.lateralus || this

        /**
         * A map of functions or string references to functions that will
         * handle [events](http://backbonejs.org/#Events) emitted by
         * `this.model`.
         *
         *     var ExtendedComponent = Lateralus.View.extend({
         *       modelEvents: {
         *         changed:someProperty: function (model, someProperty) {
         *           // ...
         *         }
         *       }
         *     });
         * @property modelEvents
         * @type {Object|undefined}
         * @default undefined
         */
        ,modelEvents: this.model
      }, function (subject, mapName) {

      if (!subject) {
        return;
      }

      var eventMap = this[mapName];

      for (var key in eventMap) {
        var method = eventMap[key];
        if (!_.isFunction(method)) {
          method = this[eventMap[key]];
        }

        if (!method) {
          new Error('Method "' + method + '" not found for ' + this.toString());
        }

        var match = key.match(delegateEventSplitter);
        var eventName = match[1];
        var boundMethod = _.bind(method, this);

        if (isLateralus(this) && isLateralus(subject)) {
          this.on(eventName, boundMethod);
        } else {
          this.listenTo(subject, eventName, boundMethod);
        }

      }
    }, this);

    return this;
  };

  /**
   * Helper function for initModel and initCollection.
   * @param {Object} [initialObject]
   * @return {{ lateralus: Lateralus, component: Lateralus.Component= }}
   * component is not defined if `this` is the Lateralus instance.
   */
  function getAugmentedOptionsObject (initialObject) {
    // jshint validthis:true
    var thisIsLateralus = isLateralus(this);
    var augmentedOptions = _.extend(initialObject || {}, {
      lateralus: thisIsLateralus ? this : this.lateralus
    });

    if (!thisIsLateralus) {
      augmentedOptions.component = this.component || this;
    }

    return augmentedOptions;
  }

  /**
   * @param {Lateralus.Component.Model} Model A constructor, not an instance.
   * @param {Object} [attributes]
   * @param {Object} [options]
   * @return {Lateralus.Component.Model} An instance of the provided Model
   * constructor.
   * @method initModel
   */
  mixins.initModel = function (Model, attributes, options) {
    var augmentedOptions = getAugmentedOptionsObject.call(this, options);
    return new Model(attributes, augmentedOptions);
  };

  /**
   * @param {Lateralus.Component.Collection} Collection A constructor, not an
   * instance.
   * @param {Array.(Lateralus.Model)} [models]
   * @param {Object} [options]
   * @return {Lateralus.Component.Collection} Am instance of the provided
   * Collection constructor.
   * @method initCollection
   */
  mixins.initCollection = function (Collection, models, options) {
    var augmentedOptions = getAugmentedOptionsObject.call(this, options);
    return new Collection(models, augmentedOptions);
  };

  /**
   * Merge the properties of another object into this object.  If the `mixin`
   * configuration object has a method called `initialize`, it is called in the
   * context of the object calling this function.
   * @method mixin
   * @param {Object} mixin The object to mix in to this one.
   */
  mixins.mixin = function (mixin) {
    _.extend(this, _.omit(mixin, 'initialize'));

    if (typeof mixin.initialize === 'function') {
      mixin.initialize.call(this);
    }
  };

  return mixins;
});
<commit_msg>Fix docs for delegateLateralusEvents.<commit_after>define([

  'underscore'

], function (

  _

) {
  'use strict';

  /**
   * These method are mixed into `{{#crossLink
   * "Lateralus.Component"}}{{/crossLink}}` and `{{#crossLink
   * "Lateralus.Component.View"}}{{/crossLink}}`.
   * @class Lateralus.mixins
   * @requires http://backbonejs.org/#Events
   */
  var mixins = {};

  /**
   * @param {Object} obj
   * @return {boolean}
   */
  function isLateralus (obj) {
    return obj.toString() === 'lateralus';
  }

  /**
   * Add a subcomponent to a `{{#crossLink "Lateralus"}}{{/crossLink}}` or
   * `{{#crossLink "Lateralus.Component"}}{{/crossLink}}` instance.
   *
   *     var App = Lateralus.beget(function () {
   *       Lateralus.apply(this, arguments);
   *     });
   *
   *     var app = new App(document.getElementById('app'));
   *     var component = app.addComponent(Lateralus.Component);
   *     var subcomponent = component.addComponent(Lateralus.Component);
   * @method addComponent
   * @param {Lateralus.Component} Component A constructor, not an instance.
   * @param {Object} [viewOptions] The `options` object to be passed along to
   * the `Component` parameter's {{#crossLink
   * "Lateralus.Component.View"}}{{/crossLink}} instance.
   * @param {Object} [options] Gets passed to the new {{#crossLink
   * "Lateralus.Component"}}{{/crossLink}} instance.
   * @param {Object} [options.modelAttributes] Any attributes to pre-populate
   * the `{{#crossLink "Lateralus.Component/Model:property"}}{{/crossLink}}`
   * instance with, if there is one.
   * @param {Object} [options.modelOptions] Any parameters to pass to the
   * `{{#crossLink "Lateralus.Component/Model:property"}}{{/crossLink}}`
   * instance, if there is one.
   * @return {Lateralus.Component} The component that was added.
   */
  mixins.addComponent = function (Component, viewOptions, options) {
    options = options || {};

    if (typeof this.component !== 'undefined') {
      return this.component.addComponent.apply(this.component, arguments);
    }

    if (!this.components) {
      /**
       * The subcomponents belonging to this object.  Do not modify this
       * property directly, it is managed by Lateralus.
       * @property components
       * @type {Object(Lateralus.Component)}
       */
      this.components = {};

      /**
       * An internal counter registry of the subcomponents belonging to this
       * object.
       * @property componentCounters
       * @type {Object(number)}
       * @private
       */
      this.componentCounters = {};
    }

    // If thisIsLateralus is false, `this` is a Lateralus.Component instance.
    var thisIsLateralus = isLateralus(this);

    var lateralusReference = thisIsLateralus ? this : this.lateralus;
    var component = new Component(
      lateralusReference
      ,options
      ,viewOptions || {}
      ,thisIsLateralus ? null : this
    );

    if (thisIsLateralus && component.view) {
      this.$el.append(component.view.$el);
    }

    var componentType = component.toString();
    if (this.componentCounters.hasOwnProperty(componentType)) {
      this.componentCounters[componentType]++;
    } else {
      this.componentCounters[componentType] = 0;
    }

    var componentInstanceName =
        componentType + this.componentCounters[componentType];
    this.components[componentInstanceName] = component;

    return component;
  };

  /**
   * Components should never communicate directly with one another in order to
   * maintain a loosely-coupled architecture.  Instead, they should just
   * broadcast general messages with the [`Backbone.Events`
   * API](http://backbonejs.org/#Events).  `emit` facilitates this loose
   * coupling by firing an event that bubbles throughout the app, depending on
   * what calls it:
   *
   * * If this is called by `{{#crossLink "Lateralus"}}{{/crossLink}}`, this
   * just triggers an event on that `{{#crossLink "Lateralus"}}{{/crossLink}}`
   * instance.
   * * If this is called by a `{{#crossLink
   * "Lateralus.Component"}}{{/crossLink}}`, this triggers an event on that
   * `{{#crossLink "Lateralus.Component"}}{{/crossLink}}` as well as the
   * central `{{#crossLink "Lateralus"}}{{/crossLink}}` instance.
   * * If this is called by a `{{#crossLink
   * "Lateralus.Component.View"}}{{/crossLink}}`, this triggers an event on
   * that `{{#crossLink "Lateralus.Component.View"}}{{/crossLink}}`, the
   * `{{#crossLink "Lateralus.Component"}}{{/crossLink}}` to which it belongs,
   * and the central `{{#crossLink "Lateralus"}}{{/crossLink}}` instance.
   *
   * This method has the same method signature as
   * [`Backbone.Events.trigger`](http://backbonejs.org/#Events-trigger).
   * @method emit
   * @param {string} eventName The name of the event.
   * @param {...any} [args] Any arguments to pass along to the listeners.
   */
  mixins.emit = function () {
    var args = _.toArray(arguments);
    this.trigger.apply(this, args);

    if (isLateralus(this)) {
      return;
    }

    if (this.component) {
      this.component.trigger.apply(this.component, args);
    }

    this.lateralus.trigger.apply(this.lateralus, args);
  };

  /**
   * Listen to an event-emitting Object and amplify one of its events across
   * the {{#crossLink "Lateralus"}}{{/crossLink}} application.  Useful for
   * making plain Backbone Objects (i.e., non-Lateralus Objects) communicate
   * important information in a broader way.
   * @method amplify
   * @param {Backbone.Events} emitter The object that `trigger`s events that
   * should be amplified globally across the app.
   * @param {string} eventName The event to amplify globally across the app.
   */
  mixins.amplify = function (emitter, eventName) {
    this.listenTo(emitter, eventName, _.bind(this.emit, this, eventName));
  };

  /**
   * Listen for an event that is triggered on the central {{#crossLink
   * "Lateralus"}}{{/crossLink}} instance and bind a function handler.
   * @method listenFor
   * @param {string} event The name of the event to listen for.
   * @param {Function} callback The function handler to bind.
   */
  mixins.listenFor = function (event, callback) {
    var thisIsLateralus = isLateralus(this);
    if (thisIsLateralus) {
      this.on(event, callback);
    } else {
      this.listenTo(this.lateralus, event, callback);
    }
  };

  var delegateEventSplitter = /^(\S+)\s*(.*)$/;

  /**
   * Bind `{{#crossLink
   * "Lateralus.mixins/lateralusEvents:property"}}{{/crossLink}}`, if it is
   * defined.
   * @method delegateLateralusEvents
   * @chainable
   */
  mixins.delegateLateralusEvents = function () {

    _.each({
        /**
         * A map of functions or string references to functions that will
         * handle [events](http://backbonejs.org/#Events) dispatched to the
         * central `{{#crossLink "Lateralus"}}{{/crossLink}}` instance.
         *
         *     var ExtendedComponent = Lateralus.Component.extend({
         *       name: 'extended'
         *
         *       ,lateralusEvents: {
         *         anotherComponentChanged: 'onAnotherComponentChanged'
         *
         *         ,anotherComponentDestroyed: function () {
         *           // ...
         *         }
         *       }
         *
         *       ,onAnotherComponentChanged: function () {
         *         // ...
         *       }
         *     });
         * @property lateralusEvents
         * @type {Object|undefined}
         * @default undefined
         */
        lateralusEvents: this.lateralus || this

        /**
         * A map of functions or string references to functions that will
         * handle [events](http://backbonejs.org/#Events) emitted by
         * `this.model`.
         *
         *     var ExtendedComponent = Lateralus.View.extend({
         *       modelEvents: {
         *         changed:someProperty: function (model, someProperty) {
         *           // ...
         *         }
         *       }
         *     });
         * @property modelEvents
         * @type {Object|undefined}
         * @default undefined
         */
        ,modelEvents: this.model
      }, function (subject, mapName) {

      if (!subject) {
        return;
      }

      var eventMap = this[mapName];

      for (var key in eventMap) {
        var method = eventMap[key];
        if (!_.isFunction(method)) {
          method = this[eventMap[key]];
        }

        if (!method) {
          new Error('Method "' + method + '" not found for ' + this.toString());
        }

        var match = key.match(delegateEventSplitter);
        var eventName = match[1];
        var boundMethod = _.bind(method, this);

        if (isLateralus(this) && isLateralus(subject)) {
          this.on(eventName, boundMethod);
        } else {
          this.listenTo(subject, eventName, boundMethod);
        }

      }
    }, this);

    return this;
  };

  /**
   * Helper function for initModel and initCollection.
   * @param {Object} [initialObject]
   * @return {{ lateralus: Lateralus, component: Lateralus.Component= }}
   * component is not defined if `this` is the Lateralus instance.
   */
  function getAugmentedOptionsObject (initialObject) {
    // jshint validthis:true
    var thisIsLateralus = isLateralus(this);
    var augmentedOptions = _.extend(initialObject || {}, {
      lateralus: thisIsLateralus ? this : this.lateralus
    });

    if (!thisIsLateralus) {
      augmentedOptions.component = this.component || this;
    }

    return augmentedOptions;
  }

  /**
   * @param {Lateralus.Component.Model} Model A constructor, not an instance.
   * @param {Object} [attributes]
   * @param {Object} [options]
   * @return {Lateralus.Component.Model} An instance of the provided Model
   * constructor.
   * @method initModel
   */
  mixins.initModel = function (Model, attributes, options) {
    var augmentedOptions = getAugmentedOptionsObject.call(this, options);
    return new Model(attributes, augmentedOptions);
  };

  /**
   * @param {Lateralus.Component.Collection} Collection A constructor, not an
   * instance.
   * @param {Array.(Lateralus.Model)} [models]
   * @param {Object} [options]
   * @return {Lateralus.Component.Collection} Am instance of the provided
   * Collection constructor.
   * @method initCollection
   */
  mixins.initCollection = function (Collection, models, options) {
    var augmentedOptions = getAugmentedOptionsObject.call(this, options);
    return new Collection(models, augmentedOptions);
  };

  /**
   * Merge the properties of another object into this object.  If the `mixin`
   * configuration object has a method called `initialize`, it is called in the
   * context of the object calling this function.
   * @method mixin
   * @param {Object} mixin The object to mix in to this one.
   */
  mixins.mixin = function (mixin) {
    _.extend(this, _.omit(mixin, 'initialize'));

    if (typeof mixin.initialize === 'function') {
      mixin.initialize.call(this);
    }
  };

  return mixins;
});
<|endoftext|>


<commit_before>import React, { Component } from "react";

class Footer extends Component {
  render() {
    if (this.props.data) {
      var networks = this.props.data.social.map(function (network) {
        return (

        );
      });
    }

    return (

              Need a Developer?

              Looking for a co-op student for the Fall 2021 term? Please reach
              out!

{networks}

                Copyright ©
                {" " + new Date().getFullYear()}

    );
  }
}

export default Footer;
<commit_msg>Update footer (#39)<commit_after>import React, { Component } from "react";

class Footer extends Component {
  render() {
    if (this.props.data) {
      var networks = this.props.data.social.map(function (network) {
        return (

        );
      });
    }

    return (

              Looking to Hire?

              Looking for a contractor or freelance developer? Looking to hire a
              SWE or PM for a full time position? Please reach out below!

{networks}

                Copyright ©
                {" " + new Date().getFullYear()}

    );
  }
}

export default Footer;
<|endoftext|>