<commit_before>/*!
 * ${copyright}
 */
/**
 * Defines support rules related to the view.
 */
sap.ui.define(["sap/ui/support/library"],
	function(SupportLib) {
	"use strict";

	// shortcuts
	var Categories = SupportLib.Categories; // Accessibility, Performance, Memory, ...
	var Severity = SupportLib.Severity; // Hint, Warning, Error
	var Audiences = SupportLib.Audiences; // Control, Internal, Application

	//**********************************************************
	// Rule Definitions
	//**********************************************************

	/**
	 * Checks for wrongly configured view namespace
	 */
	var oXMLViewWrongNamespace = {
		id: "xmlViewWrongNamespace",
		audiences: [Audiences.Application],
		categories: [Categories.Functionality],
		enabled: true,
		minversion: "-",
		title: "XML View is not configured with namespace 'sap.ui.core.mvc'",
		description: "For consistency and proper resource loading, the root node of an XML view must be configured with the namespace 'mvc'",
		resolution: "Define the XML view as '<core:View ...>' and configure the XML namepspace as 'xmlns:mvc=\"sap.ui.core.mvc\"'",
		resolutionurls: [{
			text: "Documentation: Namespaces in XML Views",
			href: "https://sapui5.hana.ondemand.com/#docs/guide/2421a2c9fa574b2e937461b5313671f0.html"
		}],
		check: function(oIssueManager, oCoreFacade, oScope) {
			var aXMLViews = oScope.getElements().filter(function (oControl) { return oControl.getMetadata().getName() === "sap.ui.core.mvc.XMLView"; });
			aXMLViews.forEach(function (oXMLView) {
				if (oXMLView._xContent.namespaceURI !== "sap.ui.core.mvc") {
					var sViewName = oXMLView.getViewName().split("\.").pop();
					oIssueManager.addIssue({
						severity: Severity.Medium,
						details: "The view '" + sViewName + "' (" + oXMLView.getId() + ") is configured with namespace '" + oXMLView._xContent.namespaceURI + "' instead of 'sap.ui.core.mvc'",
						context: {
							id: oXMLView.getId()
						}
					});
				}
			});
		}
	};

	/**
	 * Checks if a default namespaces is set in an XML view
	 */
	var oXMLViewDefaultNamespace = {
		id: "xmlViewDefaultNamespace",
		audiences: [Audiences.Control, Audiences.Application],
		categories: [Categories.Performance],
		enabled: true,
		minversion: "-",
		title: "Default namespace missing in XML view",
		description: "If the default namespace is missing, the code is less readable and parsing performance may be slow",
		resolution: "Set the namespace of the control library that holds most of the controls you use as default namespace (e.g. xmlns=\"sap.m\")",
		resolutionurls: [{
			text: "Documentation: Namespaces in XML Views",
			href: "https://sapui5.hana.ondemand.com/#docs/guide/2421a2c9fa574b2e937461b5313671f0.html"
		}],
		check: function(oIssueManager, oCoreFacade, oScope) {
			var aXMLViews = oScope.getElements().filter(function (oControl) { return oControl.getMetadata().getName() === "sap.ui.core.mvc.XMLView"; });

			aXMLViews.forEach(function (oXMLView) {
				if (!oXMLView._xContent.attributes.getNamedItem("xmlns")) {
					var sViewName = oXMLView.getViewName().split("\.").pop();
					oIssueManager.addIssue({
						severity: Severity.Low,
						details: "The view '" + sViewName + "' (" + oXMLView.getId() + ") does not contain a default namespace",
						context: {
							id: oXMLView.getId()
						}
					});
				}
			});
		}
	};

	var oXMLViewLowerCaseControl = {
		id: "xmlViewLowerCaseControl",
		audiences: ["Control","Application"],
		categories: ["Performance"],
		enabled: true,
		minversion: "-",
		title: "Control tag in XML view starts with lower case",
		description: "Control tags with lower case cannot be loaded in Linux-based systems",
		resolution: "Start the Control tag with upper case",
		resolutionurls: [],
		check: function (oIssueManager, oCoreFacade, oScope) {

			//get all aggregations of each element
			var aAggregationsOfElements = oScope.getElements().map(
					function (oElement) {
						return Object.keys(oElement.getMetadata().getAllAggregations());
					}
			);
			//flatten array of arrays and filter duplicates
			var aAggregations = aAggregationsOfElements.reduce(
				function(a, b) {
					return a.concat(b);
				}).filter(
					function (x, i, a) {
						return a.indexOf(x) === i;
					});

			var aXMLViews = oScope.getElements().filter(function (oControl) {
				return oControl.getMetadata().getName() === "sap.ui.core.mvc.XMLView";
			});

			aXMLViews.forEach(function (oXMLView) {
				var aLocalName = [];
				var _getTags = function (oXcontent) {
					aLocalName.push(oXcontent.localName);
					for (var i = 0; i < oXcontent.children.length; i++) {
						_getTags(oXcontent.children[i]);
					}
				};

				_getTags(oXMLView._xContent);
				aLocalName = jQuery.uniqueSort(aLocalName);

				aLocalName.forEach(function (sTag) 	{
					var sFirstLetter = sTag.charAt(0);
					// check for lowercase, aggregations are excluded
					if ((sFirstLetter.toLowerCase() === sFirstLetter) && !aAggregations.includes(sTag)) {
						var sViewName = oXMLView.getViewName().split("\.").pop();
						oIssueManager.addIssue({
							severity: Severity.High,
							details: "View '" + sViewName + "' (" + oXMLView.getId() + ") contains a Control tag that starts with lower case '" + sTag + "'",
							context: {
								id: oXMLView.getId()
							}
						});
					}
				});
			});
		}
	};

	/**
	 * Checks for unused namespaces inside an XML view
	 */
	var oXMLViewUnusedNamespaces = {
		id: "xmlViewUnusedNamespaces",
		audiences: [Audiences.Control, Audiences.Application],
		categories: [Categories.Usability],
		enabled: true,
		minversion: "-",
		title: "Unused namespaces in XML view",
		description: "Namespaces that are declared but not used may confuse readers of the code",
		resolution: "Remove the unused namespaces from the view definition",
		resolutionurls: [{
			text: "Documentation: Namespaces in XML Views",
			href: "https://sapui5.hana.ondemand.com/#docs/guide/2421a2c9fa574b2e937461b5313671f0.html"
		}],
		check: function(oIssueManager, oCoreFacade, oScope) {
			var aXMLViews = oScope.getElements().filter(function (oControl) { return oControl.getMetadata().getName() === "sap.ui.core.mvc.XMLView"; });

			aXMLViews.forEach(function (oXMLView) {
				for (var i = 0; i < oXMLView._xContent.attributes.length; i++) {
					var sName = oXMLView._xContent.attributes.item(i).name;
					var sLocalName = oXMLView._xContent.attributes.item(i).localName;
					var sFullName = oXMLView._xContent.attributes.item(i).value;

					// check all explicit namespaces except for the injected support namespace
					// and the mvc, because the use of mvc is checked in other rule
					if (sName.match("xmlns:")
						&& sLocalName !== "xmlns:support"
						&& sLocalName !== "mvc"
						&& sFullName.indexOf("schemas.sap.com") < 0) {
							var oContent = jQuery(oXMLView._xContent)[0];
							// get the xml code of the view as a string
							// The outerHTML doesn't work with IE, so we used
							// the XMLSerializer instead
							var sContent = new XMLSerializer().serializeToString(oContent);

							// check if there is a reference of this namespace inside the view
							if (!sContent.match("<" + sLocalName + ":")) {
								var sViewName = oXMLView.getViewName().split("\.").pop();
								oIssueManager.addIssue({
									severity: Severity.Medium,
									details: "View '" + sViewName + "' (" + oXMLView.getId() + ") contains an unused XML namespace '" + sLocalName + "' referencing library '" + sFullName + "'",
									context: {
										id: oXMLView.getId()
									}
								});
							}
						}
				}
			});
		}
	};

	/**
	 * Checks for deprecated properties
	 */
	var oDeprecatedPropertyRule = {
		id: "deprecatedProperty",
		audiences: [Audiences.Application],
		categories: [Categories.Functionality],
		enabled: true,
		minversion: "1.38",
		title: "Control is using deprecated property",
		description: "Using deprecated properties should be avoided, because they are not maintained anymore",
		resolution: "Refer to the API of the element which property should be used instead.",
		resolutionurls: [{
			text: "API Reference",
			href: "https://sapui5.hana.ondemand.com/#/api/deprecated"
		}],
		check: function(oIssueManager, oCoreFacade, oScope) {
			oScope.getElementsByClassName(sap.ui.core.Element)
				.forEach(function(oElement) {

					var oMetadata = oElement.getMetadata(),
						mProperties = oMetadata.getAllProperties();

					for (var sProperty in mProperties) {
						// if property is deprecated and it is set to a different from the default value
						// Checks only the deprecated properties with defaultValue property is not null
						if (mProperties[sProperty].deprecated
							&& mProperties[sProperty].defaultValue != oElement.getProperty(sProperty)
							&& mProperties[sProperty].defaultValue !== null) {

							oIssueManager.addIssue({
								severity: Severity.Medium,
								details: "Deprecated property '" + sProperty + "' is used for element '" + oElement.getId() + "'.",
								context: {
									id: oElement.getId()
								}
							});
						}
					}
				});
		}
	};

	/**
	 * Checks for deprecated aggregations
	 */
	var oDeprecatedAggregationRule = {
		id: "deprecatedAggregation",
		audiences: [Audiences.Application],
		categories: [Categories.Functionality],
		enabled: true,
		minversion: "1.38",
		title: "Control is using deprecated aggregation",
		description: "Using deprecated aggregation should be avoided, because they are not maintained anymore",
		resolution: "Refer to the API of the element which aggregation should be used instead.",
		resolutionurls: [{
			text: "API Reference",
			href: "https://sapui5.hana.ondemand.com/#/api/deprecated"
		}],
		check: function(oIssueManager, oCoreFacade, oScope) {
			oScope.getElementsByClassName(sap.ui.core.Element)
				.forEach(function(oElement) {

					var oMetadata = oElement.getMetadata(),
						mAggregations = oMetadata.getAllAggregations();

					for (var sAggregation in mAggregations) {
						// if aggregation is deprecated and contains elements
						if (mAggregations[sAggregation].deprecated
							&& !jQuery.isEmptyObject(oElement.getAggregation(sAggregation))) {

							oIssueManager.addIssue({
								severity: Severity.Medium,
								details: "Deprecated aggregation '" + sAggregation + "' is used for element '" + oElement.getId() + "'.",
								context: {
									id: oElement.getId()
								}
							});
						}
					}
				});
		}
	};

	/**
	 * Checks for deprecated associations
	 */
	var oDeprecatedAssociationRule = {
		id: "deprecatedAssociation",
		audiences: [Audiences.Application],
		categories: [Categories.Functionality],
		enabled: true,
		minversion: "1.38",
		title: "Control is using deprecated association",
		description: "Using deprecated association should be avoided, because they are not maintained anymore",
		resolution: "Refer to the API of the element which association should be used instead.",
		resolutionurls: [{
			text: "API Reference",
			href: "https://sapui5.hana.ondemand.com/#/api/deprecated"
		}],
		check: function(oIssueManager, oCoreFacade, oScope) {
			oScope.getElementsByClassName(sap.ui.core.Element)
				.forEach(function(oElement) {

					var oMetadata = oElement.getMetadata(),
						mAssociations = oMetadata.getAllAssociations();

					for (var sAssociation in mAssociations) {
						// if association is deprecated and set by developer
						if (mAssociations[sAssociation].deprecated
							&& !jQuery.isEmptyObject(oElement.getAssociation(sAssociation))) {

							oIssueManager.addIssue({
								severity: Severity.Medium,
								details: "Deprecated association '" + sAssociation + "' is used for element '" + oElement.getId() + "'.",
								context: {
									id: oElement.getId()
								}
							});
						}
					}
				});
		}
	};

	/**
	 * Checks for deprecated events
	 */
	var oDeprecatedEventRule = {
		id: "deprecatedEvent",
		audiences: [Audiences.Application],
		categories: [Categories.Functionality],
		enabled: true,
		minversion: "1.38",
		title: "Control is using deprecated event",
		description: "Using deprecated event should be avoided, because they are not maintained anymore",
		resolution: "Refer to the API of the element which event should be used instead.",
		resolutionurls: [{
			text: "API Reference",
			href: "https://sapui5.hana.ondemand.com/#/api/deprecated"
		}],
		check: function(oIssueManager, oCoreFacade, oScope) {
			oScope.getElementsByClassName(sap.ui.core.Element)
				.forEach(function(oElement) {

					var oMetadata = oElement.getMetadata(),
						mEvents = oMetadata.getAllEvents();

					for (var sEvent in mEvents) {
						// if event is deprecated and developer added event handler
						if (mEvents[sEvent].deprecated
							&& oElement.mEventRegistry[sEvent] && oElement.mEventRegistry[sEvent].length > 0) {

							oIssueManager.addIssue({
								severity: Severity.Medium,
								details: "Deprecated event '" + sEvent + "' is used for element '" + oElement.getId() + "'.",
								context: {
									id: oElement.getId()
								}
							});
						}
					}
				});
		}
	};

	return [
		oXMLViewWrongNamespace,
		oXMLViewDefaultNamespace,
		oXMLViewLowerCaseControl,
		oXMLViewUnusedNamespaces,
		oDeprecatedPropertyRule,
		oDeprecatedAggregationRule,
		oDeprecatedAssociationRule,
		oDeprecatedEventRule
	];
}, true);<commit_msg>[INTERNAL] ui.support: suggest mvc instead of core<commit_after>/*!
 * ${copyright}
 */
/**
 * Defines support rules related to the view.
 */
sap.ui.define(["sap/ui/support/library"],
	function(SupportLib) {
	"use strict";

	// shortcuts
	var Categories = SupportLib.Categories; // Accessibility, Performance, Memory, ...
	var Severity = SupportLib.Severity; // Hint, Warning, Error
	var Audiences = SupportLib.Audiences; // Control, Internal, Application

	//**********************************************************
	// Rule Definitions
	//**********************************************************

	/**
	 * Checks for wrongly configured view namespace
	 */
	var oXMLViewWrongNamespace = {
		id: "xmlViewWrongNamespace",
		audiences: [Audiences.Application],
		categories: [Categories.Functionality],
		enabled: true,
		minversion: "-",
		title: "XML View is not configured with namespace 'sap.ui.core.mvc'",
		description: "For consistency and proper resource loading, the root node of an XML view must be configured with the namespace 'mvc'",
		resolution: "Define the XML view as '<mvc:View ...>' and configure the XML namepspace as 'xmlns:mvc=\"sap.ui.core.mvc\"'",
		resolutionurls: [{
			text: "Documentation: Namespaces in XML Views",
			href: "https://sapui5.hana.ondemand.com/#docs/guide/2421a2c9fa574b2e937461b5313671f0.html"
		}],
		check: function(oIssueManager, oCoreFacade, oScope) {
			var aXMLViews = oScope.getElements().filter(function (oControl) { return oControl.getMetadata().getName() === "sap.ui.core.mvc.XMLView"; });
			aXMLViews.forEach(function (oXMLView) {
				if (oXMLView._xContent.namespaceURI !== "sap.ui.core.mvc") {
					var sViewName = oXMLView.getViewName().split("\.").pop();
					oIssueManager.addIssue({
						severity: Severity.Medium,
						details: "The view '" + sViewName + "' (" + oXMLView.getId() + ") is configured with namespace '" + oXMLView._xContent.namespaceURI + "' instead of 'sap.ui.core.mvc'",
						context: {
							id: oXMLView.getId()
						}
					});
				}
			});
		}
	};

	/**
	 * Checks if a default namespaces is set in an XML view
	 */
	var oXMLViewDefaultNamespace = {
		id: "xmlViewDefaultNamespace",
		audiences: [Audiences.Control, Audiences.Application],
		categories: [Categories.Performance],
		enabled: true,
		minversion: "-",
		title: "Default namespace missing in XML view",
		description: "If the default namespace is missing, the code is less readable and parsing performance may be slow",
		resolution: "Set the namespace of the control library that holds most of the controls you use as default namespace (e.g. xmlns=\"sap.m\")",
		resolutionurls: [{
			text: "Documentation: Namespaces in XML Views",
			href: "https://sapui5.hana.ondemand.com/#docs/guide/2421a2c9fa574b2e937461b5313671f0.html"
		}],
		check: function(oIssueManager, oCoreFacade, oScope) {
			var aXMLViews = oScope.getElements().filter(function (oControl) { return oControl.getMetadata().getName() === "sap.ui.core.mvc.XMLView"; });

			aXMLViews.forEach(function (oXMLView) {
				if (!oXMLView._xContent.attributes.getNamedItem("xmlns")) {
					var sViewName = oXMLView.getViewName().split("\.").pop();
					oIssueManager.addIssue({
						severity: Severity.Low,
						details: "The view '" + sViewName + "' (" + oXMLView.getId() + ") does not contain a default namespace",
						context: {
							id: oXMLView.getId()
						}
					});
				}
			});
		}
	};

	var oXMLViewLowerCaseControl = {
		id: "xmlViewLowerCaseControl",
		audiences: ["Control","Application"],
		categories: ["Performance"],
		enabled: true,
		minversion: "-",
		title: "Control tag in XML view starts with lower case",
		description: "Control tags with lower case cannot be loaded in Linux-based systems",
		resolution: "Start the Control tag with upper case",
		resolutionurls: [],
		check: function (oIssueManager, oCoreFacade, oScope) {

			//get all aggregations of each element
			var aAggregationsOfElements = oScope.getElements().map(
					function (oElement) {
						return Object.keys(oElement.getMetadata().getAllAggregations());
					}
			);
			//flatten array of arrays and filter duplicates
			var aAggregations = aAggregationsOfElements.reduce(
				function(a, b) {
					return a.concat(b);
				}).filter(
					function (x, i, a) {
						return a.indexOf(x) === i;
					});

			var aXMLViews = oScope.getElements().filter(function (oControl) {
				return oControl.getMetadata().getName() === "sap.ui.core.mvc.XMLView";
			});

			aXMLViews.forEach(function (oXMLView) {
				var aLocalName = [];
				var _getTags = function (oXcontent) {
					aLocalName.push(oXcontent.localName);
					for (var i = 0; i < oXcontent.children.length; i++) {
						_getTags(oXcontent.children[i]);
					}
				};

				_getTags(oXMLView._xContent);
				aLocalName = jQuery.uniqueSort(aLocalName);

				aLocalName.forEach(function (sTag) 	{
					var sFirstLetter = sTag.charAt(0);
					// check for lowercase, aggregations are excluded
					if ((sFirstLetter.toLowerCase() === sFirstLetter) && !aAggregations.includes(sTag)) {
						var sViewName = oXMLView.getViewName().split("\.").pop();
						oIssueManager.addIssue({
							severity: Severity.High,
							details: "View '" + sViewName + "' (" + oXMLView.getId() + ") contains a Control tag that starts with lower case '" + sTag + "'",
							context: {
								id: oXMLView.getId()
							}
						});
					}
				});
			});
		}
	};

	/**
	 * Checks for unused namespaces inside an XML view
	 */
	var oXMLViewUnusedNamespaces = {
		id: "xmlViewUnusedNamespaces",
		audiences: [Audiences.Control, Audiences.Application],
		categories: [Categories.Usability],
		enabled: true,
		minversion: "-",
		title: "Unused namespaces in XML view",
		description: "Namespaces that are declared but not used may confuse readers of the code",
		resolution: "Remove the unused namespaces from the view definition",
		resolutionurls: [{
			text: "Documentation: Namespaces in XML Views",
			href: "https://sapui5.hana.ondemand.com/#docs/guide/2421a2c9fa574b2e937461b5313671f0.html"
		}],
		check: function(oIssueManager, oCoreFacade, oScope) {
			var aXMLViews = oScope.getElements().filter(function (oControl) { return oControl.getMetadata().getName() === "sap.ui.core.mvc.XMLView"; });

			aXMLViews.forEach(function (oXMLView) {
				for (var i = 0; i < oXMLView._xContent.attributes.length; i++) {
					var sName = oXMLView._xContent.attributes.item(i).name;
					var sLocalName = oXMLView._xContent.attributes.item(i).localName;
					var sFullName = oXMLView._xContent.attributes.item(i).value;

					// check all explicit namespaces except for the injected support namespace
					// and the mvc, because the use of mvc is checked in other rule
					if (sName.match("xmlns:")
						&& sLocalName !== "xmlns:support"
						&& sLocalName !== "mvc"
						&& sFullName.indexOf("schemas.sap.com") < 0) {
							var oContent = jQuery(oXMLView._xContent)[0];
							// get the xml code of the view as a string
							// The outerHTML doesn't work with IE, so we used
							// the XMLSerializer instead
							var sContent = new XMLSerializer().serializeToString(oContent);

							// check if there is a reference of this namespace inside the view
							if (!sContent.match("<" + sLocalName + ":")) {
								var sViewName = oXMLView.getViewName().split("\.").pop();
								oIssueManager.addIssue({
									severity: Severity.Medium,
									details: "View '" + sViewName + "' (" + oXMLView.getId() + ") contains an unused XML namespace '" + sLocalName + "' referencing library '" + sFullName + "'",
									context: {
										id: oXMLView.getId()
									}
								});
							}
						}
				}
			});
		}
	};

	/**
	 * Checks for deprecated properties
	 */
	var oDeprecatedPropertyRule = {
		id: "deprecatedProperty",
		audiences: [Audiences.Application],
		categories: [Categories.Functionality],
		enabled: true,
		minversion: "1.38",
		title: "Control is using deprecated property",
		description: "Using deprecated properties should be avoided, because they are not maintained anymore",
		resolution: "Refer to the API of the element which property should be used instead.",
		resolutionurls: [{
			text: "API Reference",
			href: "https://sapui5.hana.ondemand.com/#/api/deprecated"
		}],
		check: function(oIssueManager, oCoreFacade, oScope) {
			oScope.getElementsByClassName(sap.ui.core.Element)
				.forEach(function(oElement) {

					var oMetadata = oElement.getMetadata(),
						mProperties = oMetadata.getAllProperties();

					for (var sProperty in mProperties) {
						// if property is deprecated and it is set to a different from the default value
						// Checks only the deprecated properties with defaultValue property is not null
						if (mProperties[sProperty].deprecated
							&& mProperties[sProperty].defaultValue != oElement.getProperty(sProperty)
							&& mProperties[sProperty].defaultValue !== null) {

							oIssueManager.addIssue({
								severity: Severity.Medium,
								details: "Deprecated property '" + sProperty + "' is used for element '" + oElement.getId() + "'.",
								context: {
									id: oElement.getId()
								}
							});
						}
					}
				});
		}
	};

	/**
	 * Checks for deprecated aggregations
	 */
	var oDeprecatedAggregationRule = {
		id: "deprecatedAggregation",
		audiences: [Audiences.Application],
		categories: [Categories.Functionality],
		enabled: true,
		minversion: "1.38",
		title: "Control is using deprecated aggregation",
		description: "Using deprecated aggregation should be avoided, because they are not maintained anymore",
		resolution: "Refer to the API of the element which aggregation should be used instead.",
		resolutionurls: [{
			text: "API Reference",
			href: "https://sapui5.hana.ondemand.com/#/api/deprecated"
		}],
		check: function(oIssueManager, oCoreFacade, oScope) {
			oScope.getElementsByClassName(sap.ui.core.Element)
				.forEach(function(oElement) {

					var oMetadata = oElement.getMetadata(),
						mAggregations = oMetadata.getAllAggregations();

					for (var sAggregation in mAggregations) {
						// if aggregation is deprecated and contains elements
						if (mAggregations[sAggregation].deprecated
							&& !jQuery.isEmptyObject(oElement.getAggregation(sAggregation))) {

							oIssueManager.addIssue({
								severity: Severity.Medium,
								details: "Deprecated aggregation '" + sAggregation + "' is used for element '" + oElement.getId() + "'.",
								context: {
									id: oElement.getId()
								}
							});
						}
					}
				});
		}
	};

	/**
	 * Checks for deprecated associations
	 */
	var oDeprecatedAssociationRule = {
		id: "deprecatedAssociation",
		audiences: [Audiences.Application],
		categories: [Categories.Functionality],
		enabled: true,
		minversion: "1.38",
		title: "Control is using deprecated association",
		description: "Using deprecated association should be avoided, because they are not maintained anymore",
		resolution: "Refer to the API of the element which association should be used instead.",
		resolutionurls: [{
			text: "API Reference",
			href: "https://sapui5.hana.ondemand.com/#/api/deprecated"
		}],
		check: function(oIssueManager, oCoreFacade, oScope) {
			oScope.getElementsByClassName(sap.ui.core.Element)
				.forEach(function(oElement) {

					var oMetadata = oElement.getMetadata(),
						mAssociations = oMetadata.getAllAssociations();

					for (var sAssociation in mAssociations) {
						// if association is deprecated and set by developer
						if (mAssociations[sAssociation].deprecated
							&& !jQuery.isEmptyObject(oElement.getAssociation(sAssociation))) {

							oIssueManager.addIssue({
								severity: Severity.Medium,
								details: "Deprecated association '" + sAssociation + "' is used for element '" + oElement.getId() + "'.",
								context: {
									id: oElement.getId()
								}
							});
						}
					}
				});
		}
	};

	/**
	 * Checks for deprecated events
	 */
	var oDeprecatedEventRule = {
		id: "deprecatedEvent",
		audiences: [Audiences.Application],
		categories: [Categories.Functionality],
		enabled: true,
		minversion: "1.38",
		title: "Control is using deprecated event",
		description: "Using deprecated event should be avoided, because they are not maintained anymore",
		resolution: "Refer to the API of the element which event should be used instead.",
		resolutionurls: [{
			text: "API Reference",
			href: "https://sapui5.hana.ondemand.com/#/api/deprecated"
		}],
		check: function(oIssueManager, oCoreFacade, oScope) {
			oScope.getElementsByClassName(sap.ui.core.Element)
				.forEach(function(oElement) {

					var oMetadata = oElement.getMetadata(),
						mEvents = oMetadata.getAllEvents();

					for (var sEvent in mEvents) {
						// if event is deprecated and developer added event handler
						if (mEvents[sEvent].deprecated
							&& oElement.mEventRegistry[sEvent] && oElement.mEventRegistry[sEvent].length > 0) {

							oIssueManager.addIssue({
								severity: Severity.Medium,
								details: "Deprecated event '" + sEvent + "' is used for element '" + oElement.getId() + "'.",
								context: {
									id: oElement.getId()
								}
							});
						}
					}
				});
		}
	};

	return [
		oXMLViewWrongNamespace,
		oXMLViewDefaultNamespace,
		oXMLViewLowerCaseControl,
		oXMLViewUnusedNamespaces,
		oDeprecatedPropertyRule,
		oDeprecatedAggregationRule,
		oDeprecatedAssociationRule,
		oDeprecatedEventRule
	];
}, true);
<|endoftext|>


<commit_before>if (browser.contextualIdentities !== undefined) {
  browser.contextualIdentities.query({})
    .then((contexts) => {
      const parentId = chrome.contextMenus.create({
        id: "moveContext",
        title: "Move to context",
        contexts: ["tab"]
      });

      const contextStore = contexts.reduce((store, context) => {
        return Object.assign({}, store, {
          [`contextPlus-${context.name}`]: context.cookieStoreId
        });
      }, {});

      contexts.forEach(context => {
        chrome.contextMenus.create({
          type: "normal",
          title: context.name,
          id: `contextPlus-${context.name}`,
          parentId
        });
      });

      browser.contextMenus.onClicked.addListener(function (info, tab) {
        if (contextStore.hasOwnProperty(info.menuItemId)) {
          const cookieStoreId = contextStore[info.menuItemId];
          const newTabData = {
            active,
            index,
            pinned,
            url,
            windowId
          } = tab;

          browser.tabs.create({
            active,
            cookieStoreId,
            index,
            pinned,
            url,
            windowId
          }).then(() => browser.tabs.remove(tab.id));
        }
      });
    });
}
<commit_msg>Allow for switching to default context<commit_after>if (browser.contextualIdentities !== undefined) {
  browser.contextualIdentities.query({})
    .then((contexts) => {
      const parentId = chrome.contextMenus.create({
        id: "moveContext",
        title: "Move to context",
        contexts: ["tab"]
      });

      const contextStore = contexts.reduce((store, context) => {
        return Object.assign({}, store, {
          [`contextPlus-${context.name}`]: context.cookieStoreId
        });
      }, {});

      // Add a default context manually.
      // Hope the name of the default cookieStore never changes :)
      contextStore['contextPlus-default'] = 'firefox-default';
      browser.contextMenus.create({
        type: "normal",
        title: "Default",
        id: 'contextPlus-default',
        parentId
      });

      contexts.forEach(context => {
        chrome.contextMenus.create({
          type: "normal",
          title: context.name,
          id: `contextPlus-${context.name}`,
          parentId
        });
      });

      browser.contextMenus.onClicked.addListener(function (info, tab) {
        if (contextStore.hasOwnProperty(info.menuItemId)) {
          const cookieStoreId = contextStore[info.menuItemId];
          const newTabData = {
            active,
            index,
            pinned,
            url,
            windowId
          } = tab;

          browser.tabs.create({
            active,
            cookieStoreId,
            index,
            pinned,
            url,
            windowId
          }).then(() => browser.tabs.remove(tab.id));
        }
      });
    });
}
<|endoftext|>


<commit_before>/**
 * Copyright 2015 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import test from 'ava';
import isPlainObj from 'is-plain-obj';
import tempWrite from 'temp-write';
import eslint from 'eslint';
import conf from '../';

function runEslint(str, conf) {
  const linter = new eslint.CLIEngine({
    useEslintrc: false,
    configFile: tempWrite.sync(JSON.stringify(conf))
  });

  return linter.executeOnText(str).results[0].messages;
}

test(t => {
  t.true(isPlainObj(conf));
  t.true(isPlainObj(conf.rules));

  const errors = runEslint(`'use strict'\nvar foo = function () {};\nfoo();\n`, conf);
  t.is(errors[0].ruleId, 'semi');
  t.is(errors[1].ruleId, 'space-before-function-paren');
});
<commit_msg>fix bug in test<commit_after>/**
 * Copyright 2015 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import test from 'ava';
import isPlainObj from 'is-plain-obj';
import tempWrite from 'temp-write';
import eslint from 'eslint';
import conf from '../';

function runEslint(str, conf) {
  const linter = new eslint.CLIEngine({
    useEslintrc: false,
    configFile: tempWrite.sync(JSON.stringify(conf))
  });

  return linter.executeOnText(str).results[0].messages;
}

test(t => {
  t.true(isPlainObj(conf));
  t.true(isPlainObj(conf.rules));

  const errors = runEslint(`'use strict'\nvar foo = function () {};\nfoo();\n`, conf);

  t.is(errors[0].ruleId, 'semi');
  t.is(errors[1].ruleId, 'newline-after-var');
  t.is(errors[2].ruleId, 'func-names');
  t.is(errors[3].ruleId, 'space-before-function-paren');
});
<|endoftext|>


<commit_before>"use strict";

const $ = require("jquery");
const socket = require("../socket");
const templates = require("../../views");
const options = require("../options");

socket.on("msg:preview", function(data) {
	data.preview.shown = options.shouldOpenMessagePreview(data.preview.type);

	const msg = $("#msg-" + data.id);
	const container = msg.parent(".messages");
	const bottom = container.isScrollBottom();

	msg.find(".text").append(templates.msg_preview({preview: data.preview}));

	if (data.preview.shown && bottom) {
		handleImageInPreview(msg.find(".toggle-content"), container);
	}

	container.trigger("keepToBottom");
});

$("#chat").on("click", ".toggle-button", function() {
	const self = $(this);
	const container = self.closest(".messages");
	const content = self.parent().next(".toggle-content");
	const bottom = container.isScrollBottom();

	if (bottom && !content.hasClass("show")) {
		handleImageInPreview(content, container);
	}

	content.toggleClass("show");

	// If scrollbar was at the bottom before toggling the preview, keep it at the bottom
	if (bottom) {
		container.scrollBottom();
	}
});

function handleImageInPreview(content, container) {
	const img = content.find("img");

	// Trigger scroll logic after the image loads
	if (img.length && !img.width()) {
		img.on("load", function() {
			container.trigger("keepToBottom");
		});
	}
}
<commit_msg>Fix jumps when toggling link preview<commit_after>"use strict";

const $ = require("jquery");
const socket = require("../socket");
const templates = require("../../views");
const options = require("../options");

socket.on("msg:preview", function(data) {
	data.preview.shown = options.shouldOpenMessagePreview(data.preview.type);

	const msg = $("#msg-" + data.id);
	const container = msg.closest(".chat");
	const bottom = container.isScrollBottom();

	msg.find(".text").append(templates.msg_preview({preview: data.preview}));

	if (data.preview.shown && bottom) {
		handleImageInPreview(msg.find(".toggle-content"), container);
	}

	container.trigger("keepToBottom");
});

$("#chat").on("click", ".toggle-button", function() {
	const self = $(this);
	const container = self.closest(".chat");
	const content = self.parent().next(".toggle-content");
	const bottom = container.isScrollBottom();

	if (bottom && !content.hasClass("show")) {
		handleImageInPreview(content, container);
	}

	content.toggleClass("show");

	// If scrollbar was at the bottom before toggling the preview, keep it at the bottom
	if (bottom) {
		container.scrollBottom();
	}
});

function handleImageInPreview(content, container) {
	const img = content.find("img");

	// Trigger scroll logic after the image loads
	if (img.length && !img.width()) {
		img.on("load", function() {
			container.trigger("keepToBottom");
		});
	}
}
<|endoftext|>


<commit_before>/*
 * test.lb.core.Module.js - Unit Tests of Core Module
 *
 * Author:    Eric Bréchemier <legalbox@eric.brechemier.name>
 * Copyright: Legal Box (c) 2010, All Rights Reserved
 * Version:   2010-05-17
 *
 * Based on Test Runner from bezen.org JavaScript library
 * CC-BY: Eric Bréchemier - http://bezen.org/javascript/
 */

/*requires lb.core.Module.js */
/*requires bezen.js */
/*requires bezen.assert.js */
/*requires bezen.object.js */
/*requires bezen.string.js */
/*requires bezen.testrunner.js */
/*jslint nomen:false, white:false, onevar:false, plusplus:false */
/*global lb, bezen, window */
(function() {
  // Builder of
  // Closure object for Test of Core Module

  // Define aliases
  var assert = bezen.assert,
      object = bezen.object,
      string = bezen.string,
      testrunner = bezen.testrunner,
      $ = bezen.$;

  function testNamespace(){

    assert.isTrue( object.exists(window,'lb','core','Module'),
                                    "lb.core.Module namespace was not found");
  }

  var sandboxCreated;
  var startCounter = 0;
  var endCounter = 0;

  function createStubModule(sandbox){
    // create a new stub module, for Unit Tests purpose,
    // keeping track of calls to start() and end() and provided sandbox

    sandboxCreated = sandbox;
    return {
      start: function(){ startCounter++; },
      end: function(){ endCounter++; }
    };
  }

  function creatorWhichFails(sandbox){
    // a creator function that throws an exception

    throw new Error('Test failure in creator');
  }

  function createModuleWhichFailsToStart(sandbox){
    // create a new stub module which fails to start

    return {
      start: function(){ throw new Error('Test failure in start'); }
    };
  }

  function createModuleWhichFailsToEnd(sandbox){
    // create a new stub module which fails to end

    return {
      start: function(){},
      end: function(){ throw new Error('Test failure in end'); }
    };
  }

  function createLazyModule(sandbox){
    // create a new stub module which was too lazy to declare start() and end()

    return {};
  }

  function testConstructor(){
    var Ut = lb.core.Module;

    sandboxCreated = null;
    var module = new Ut('testConstructor', createStubModule);
    assert.isTrue( object.exists(sandboxCreated),
                  "underlying module must be created with a sandbox instance");

    module = new lb.core.Module('testConstructor.fail', creatorWhichFails);
  }

  function testGetId(){
    // Unit tests for lb.core.Module#getId()

    var id = 'testGetId';
    var module = new lb.core.Module(id, createStubModule);
    assert.equals( module.getId(), id,
                     "getId expected to return the id given in constructor");
  }

  function testGetSandbox(){
    var ut = new lb.core.Module('testGetSandbox',createStubModule).getSandbox;

    assert.isTrue( object.exists( ut() ),         "sandbox object expected");
  }

  function testStart(){
    // Unit tests for lb.core.Module#start()

    var module = new lb.core.Module('testStart', createStubModule);

    startCounter = 0;
    module.start();
    assert.equals(startCounter, 1, "Underlying module expected to be started");

    module = new lb.core.Module('testStart.fail', createModuleWhichFailsToStart);
    module.start();

    module = new lb.core.Module('testStart.lazy', createLazyModule);
    module.start();
  }

  function testEnd(){
    // Unit tests for lb.core.Module#end()

    var module = new lb.core.Module('testEnd', createStubModule);
    module.start();
    endCounter = 0;
    module.end();
    assert.equals(endCounter, 1, "Underlying module expected to be ended");

    module = new lb.core.Module('testEnd.listeners', createStubModule);
    var sandbox = module.getSandbox();
    sandbox.addListener( sandbox.getBox(), 'click', bezen.nix);
    module.end();
    assert.arrayEquals(sandbox.getListeners(), [],
                                              "all listeners must be removed");

    module = new lb.core.Module('testEnd', createStubModule);
    endCounter = 0;
    module.end();
    assert.equals(endCounter, 1,  "end call expected, even without a start");

    module = new lb.core.Module('testEnd.fail', createModuleWhichFailsToEnd);
    module.start();
    module.end();

    module = new lb.core.Module('testEnd.lazy', createLazyModule);
    module.end();

    module = new lb.core.Module('testEnd.lazy.listeners', createLazyModule);
    sandbox = module.getSandbox();
    sandbox.addListener( sandbox.getBox(), 'click', bezen.nix);
    module.end();
    assert.arrayEquals(sandbox.getListeners(), [],
                  "all listeners must be removed, even when end() is omitted");
  }

  var tests = {
    testNamespace: testNamespace,
    testConstructor: testConstructor,
    testGetId: testGetId,
    testGetSandbox: testGetSandbox,
    testGetSandbox: testGetSandbox,
    testStart: testStart,
    testEnd: testEnd
  };

  testrunner.define(tests, "lb.core.Module");
  return tests;

}());
<commit_msg>Removed duplicate test declaration based on JSLint error<commit_after>/*
 * test.lb.core.Module.js - Unit Tests of Core Module
 *
 * Author:    Eric Bréchemier <legalbox@eric.brechemier.name>
 * Copyright: Legal Box (c) 2010, All Rights Reserved
 * Version:   2010-05-17
 *
 * Based on Test Runner from bezen.org JavaScript library
 * CC-BY: Eric Bréchemier - http://bezen.org/javascript/
 */

/*requires lb.core.Module.js */
/*requires bezen.js */
/*requires bezen.assert.js */
/*requires bezen.object.js */
/*requires bezen.string.js */
/*requires bezen.testrunner.js */
/*jslint nomen:false, white:false, onevar:false, plusplus:false */
/*global lb, bezen, window */
(function() {
  // Builder of
  // Closure object for Test of Core Module

  // Define aliases
  var assert = bezen.assert,
      object = bezen.object,
      string = bezen.string,
      testrunner = bezen.testrunner,
      $ = bezen.$;

  function testNamespace(){

    assert.isTrue( object.exists(window,'lb','core','Module'),
                                    "lb.core.Module namespace was not found");
  }

  var sandboxCreated;
  var startCounter = 0;
  var endCounter = 0;

  function createStubModule(sandbox){
    // create a new stub module, for Unit Tests purpose,
    // keeping track of calls to start() and end() and provided sandbox

    sandboxCreated = sandbox;
    return {
      start: function(){ startCounter++; },
      end: function(){ endCounter++; }
    };
  }

  function creatorWhichFails(sandbox){
    // a creator function that throws an exception

    throw new Error('Test failure in creator');
  }

  function createModuleWhichFailsToStart(sandbox){
    // create a new stub module which fails to start

    return {
      start: function(){ throw new Error('Test failure in start'); }
    };
  }

  function createModuleWhichFailsToEnd(sandbox){
    // create a new stub module which fails to end

    return {
      start: function(){},
      end: function(){ throw new Error('Test failure in end'); }
    };
  }

  function createLazyModule(sandbox){
    // create a new stub module which was too lazy to declare start() and end()

    return {};
  }

  function testConstructor(){
    var Ut = lb.core.Module;

    sandboxCreated = null;
    var module = new Ut('testConstructor', createStubModule);
    assert.isTrue( object.exists(sandboxCreated),
                  "underlying module must be created with a sandbox instance");

    module = new lb.core.Module('testConstructor.fail', creatorWhichFails);
  }

  function testGetId(){
    // Unit tests for lb.core.Module#getId()

    var id = 'testGetId';
    var module = new lb.core.Module(id, createStubModule);
    assert.equals( module.getId(), id,
                     "getId expected to return the id given in constructor");
  }

  function testGetSandbox(){
    var ut = new lb.core.Module('testGetSandbox',createStubModule).getSandbox;

    assert.isTrue( object.exists( ut() ),         "sandbox object expected");
  }

  function testStart(){
    // Unit tests for lb.core.Module#start()

    var module = new lb.core.Module('testStart', createStubModule);

    startCounter = 0;
    module.start();
    assert.equals(startCounter, 1, "Underlying module expected to be started");

    module = new lb.core.Module('testStart.fail', createModuleWhichFailsToStart);
    module.start();

    module = new lb.core.Module('testStart.lazy', createLazyModule);
    module.start();
  }

  function testEnd(){
    // Unit tests for lb.core.Module#end()

    var module = new lb.core.Module('testEnd', createStubModule);
    module.start();
    endCounter = 0;
    module.end();
    assert.equals(endCounter, 1, "Underlying module expected to be ended");

    module = new lb.core.Module('testEnd.listeners', createStubModule);
    var sandbox = module.getSandbox();
    sandbox.addListener( sandbox.getBox(), 'click', bezen.nix);
    module.end();
    assert.arrayEquals(sandbox.getListeners(), [],
                                              "all listeners must be removed");

    module = new lb.core.Module('testEnd', createStubModule);
    endCounter = 0;
    module.end();
    assert.equals(endCounter, 1,  "end call expected, even without a start");

    module = new lb.core.Module('testEnd.fail', createModuleWhichFailsToEnd);
    module.start();
    module.end();

    module = new lb.core.Module('testEnd.lazy', createLazyModule);
    module.end();

    module = new lb.core.Module('testEnd.lazy.listeners', createLazyModule);
    sandbox = module.getSandbox();
    sandbox.addListener( sandbox.getBox(), 'click', bezen.nix);
    module.end();
    assert.arrayEquals(sandbox.getListeners(), [],
                  "all listeners must be removed, even when end() is omitted");
  }

  var tests = {
    testNamespace: testNamespace,
    testConstructor: testConstructor,
    testGetId: testGetId,
    testGetSandbox: testGetSandbox,
    testStart: testStart,
    testEnd: testEnd
  };

  testrunner.define(tests, "lb.core.Module");
  return tests;

}());
<|endoftext|>


<commit_before>/* global AudioContext:false, signals:false, ID3:false, FileAPIReader:false */
(function() {
'use strict';

function MediaPlayer() {
	this.audio = new Audio();
	this.audio.addEventListener('ended', this._onEnded.bind(this));

	this.currentData = undefined;
	this.currentFile = undefined;
	this.currentTag = undefined;

	this.audioCtx = new AudioContext();
	this.audioSrc = this.audioCtx.createMediaElementSource(this.audio);
	this.analyser = this.audioCtx.createAnalyser();

	this.audioSrc.connect(this.analyser);
	this.analyser.connect(this.audioCtx.destination);

	this.onLoad = new signals.Signal();
	this.onPlay = new signals.Signal();
	this.onPause = new signals.Signal();
	this.onEnded = new signals.Signal();
}

window.MediaPlayer = MediaPlayer;
MediaPlayer.prototype.constructor = MediaPlayer;

MediaPlayer.prototype.load = function(file) {
	this.currentFile = file;
	var reader = new FileReader();
	reader.onload = function(e) {
		this.currentData = e.target.result;

		this.audio.src = this.currentData;

		this.play();
	}.bind(this);

	reader.readAsDataURL(file);

	ID3.loadTags(file.name, function() {
	    this.currentTag = ID3.getAllTags(file.name);
	    this.onLoad.dispatch(this.currentTag);
	}.bind(this), {
	    dataReader: FileAPIReader(this.currentFile),
	    tags: ['title', 'artist', 'album', 'track', 'picture']
	});
};

MediaPlayer.prototype._onEnded = function() {
	this.onEnded.dispatch();
};

MediaPlayer.prototype.togglePause = function() {
	if (this.audio) {
		if (this.audio.paused) {
			this.play();
		} else {
			this.pause();
		}

		return this.audio.paused;
	}
	return null;
};

MediaPlayer.prototype.play = function() {
	if (this.audio) {
		this.audio.play();
		this.onPlay.dispatch();
	}
};

MediaPlayer.prototype.pause = function() {
	if (this.audio) {
		this.audio.pause();
		this.onPause.dispatch();
	}
};

Object.defineProperty(MediaPlayer.prototype, 'isLoaded', {
	get: function() {
		return this.audio && (this.audio.src !== '') && !this.audio.ended;
	}
});

Object.defineProperty(MediaPlayer.prototype, 'muted', {
	get: function() {
		return this.audio ? this.audio.muted : undefined;
	},
	set: function(value) {
		if (this.audio) {
			this.audio.muted = value;
		}
	}
});

})();<commit_msg>Audio context now uses webkit prefix<commit_after>/* global AudioContext:false, signals:false, ID3:false, FileAPIReader:false */
(function() {
'use strict';

function MediaPlayer() {
	this.audio = new Audio();
	this.audio.addEventListener('ended', this._onEnded.bind(this));

	this.currentData = undefined;
	this.currentFile = undefined;
	this.currentTag = undefined;

	this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
	this.audioSrc = this.audioCtx.createMediaElementSource(this.audio);
	this.analyser = this.audioCtx.createAnalyser();

	this.audioSrc.connect(this.analyser);
	this.analyser.connect(this.audioCtx.destination);

	this.onLoad = new signals.Signal();
	this.onPlay = new signals.Signal();
	this.onPause = new signals.Signal();
	this.onEnded = new signals.Signal();
}

window.MediaPlayer = MediaPlayer;
MediaPlayer.prototype.constructor = MediaPlayer;

MediaPlayer.prototype.load = function(file) {
	this.currentFile = file;
	var reader = new FileReader();
	reader.onload = function(e) {
		this.currentData = e.target.result;

		this.audio.src = this.currentData;

		this.play();
	}.bind(this);

	reader.readAsDataURL(file);

	ID3.loadTags(file.name, function() {
	    this.currentTag = ID3.getAllTags(file.name);
	    this.onLoad.dispatch(this.currentTag);
	}.bind(this), {
	    dataReader: FileAPIReader(this.currentFile),
	    tags: ['title', 'artist', 'album', 'track', 'picture']
	});
};

MediaPlayer.prototype._onEnded = function() {
	this.onEnded.dispatch();
};

MediaPlayer.prototype.togglePause = function() {
	if (this.audio) {
		if (this.audio.paused) {
			this.play();
		} else {
			this.pause();
		}

		return this.audio.paused;
	}
	return null;
};

MediaPlayer.prototype.play = function() {
	if (this.audio) {
		this.audio.play();
		this.onPlay.dispatch();
	}
};

MediaPlayer.prototype.pause = function() {
	if (this.audio) {
		this.audio.pause();
		this.onPause.dispatch();
	}
};

Object.defineProperty(MediaPlayer.prototype, 'isLoaded', {
	get: function() {
		return this.audio && (this.audio.src !== '') && !this.audio.ended;
	}
});

Object.defineProperty(MediaPlayer.prototype, 'muted', {
	get: function() {
		return this.audio ? this.audio.muted : undefined;
	},
	set: function(value) {
		if (this.audio) {
			this.audio.muted = value;
		}
	}
});

})();<|endoftext|>


<commit_before>var stubTransport = require('nodemailer-stub-transport');
var nodemailer = require('nodemailer');
var url = require('url');
var request = require('request');
var logger = require('../utils/logger.js');
var Utils = require('../utils/util');
var async = require('async');

if(process.env.NODE_ENV === 'test') {
	mailTransport = nodemailer.createTransport(stubTransport());
} else {
	mailTransport = nodemailer.createTransport({
		host: 'mail.stefanocappa.it',
		port: '25',
		debug: true, //this!!!
		auth: {
			user: process.env.USER_EMAIL, //secret data
			pass: process.env.PASS_EMAIL //secret data
		}
	});
}

/* POST to send an email */
/* /api/email */
module.exports.sendEmailWithRecaptcha = function(req, res) {
	console.log("verifyCaptcha api called in app-api controllers " +req.body.response);

	//complete the request with my secret key
	//as described in google documentation.
	var data = {
		secret: process.env.RECAPTCHA_SECRET,
		response: req.body.response
		//here I can add also the IP, but it's not mandatory
	};

	const RECAPTCHA_URL = 'https://www.google.com/recaptcha/api/siteverify';

	async.waterfall([
    done => {
    	request.post({url:RECAPTCHA_URL, form: data}, (err,response,body) => {
    		console.log(body);
    		done(err, body);
    	});
    },
    (body, done) => {

	    var result = JSON.parse( body );
			console.log(result);
			if(result.success === false) {
				if(result['error-codes']) {
					Utils.sendJSONres(res, 401, result['error-codes']);
				} else {
					Utils.sendJSONres(res, 401, "Recaptcha verify answered FALSE!");
				}
			} else {
				done();
			}
		},
		(done) => {

			console.log("Trying to send an email");
			if (req.body.emailFormData && req.body.emailFormData.email &&
				req.body.emailFormData.object && req.body.emailFormData.messageText) {

				console.log("Preparing to send an email");
				done(null, req.body.emailFormData);
			} else {
				Utils.sendJSONres(res, 400, 'Missing input params');
			}
		},
		(formEmail, done) => {

			console.log('Sending an email from ' + process.env.USER_EMAIL + ' to: ' + formEmail.email);

			var message = {
				from: process.env.USER_EMAIL,
				to: formEmail.email,
				subject: formEmail.object,
				html: formEmail.messageText,
				generateTextFromHtml: true
			};

			mailTransport.sendMail(message, err => {
				if (err) {
					console.log('err ----> returning 404');
					console.log(err.message);
					done(err, 404, null);
				}
				console.log('OK -----> returning 200');
				done(null, 200, formEmail);
			});
		},
		(resultHttpCode, formEmail, done) => {

			console.log("resultHttpCode: " + resultHttpCode);
			if(resultHttpCode === 200) {
				console.log('Message sent successfully to: ' + formEmail.email);
				Utils.sendJSONres(res, 200, { message: formEmail.email });
			} else {
				console.log("Error, resultHttpCode!=200 -> " + resultHttpCode);
				Utils.sendJSONres(res, 500, "Impossibile to send the email");
			}
	  }], (err) => {
	    if (err) {
	    	Utils.sendJSONres(res, 500, "Unknown error");
			}
    });
};
<commit_msg>contact doc #50<commit_after>var stubTransport = require('nodemailer-stub-transport');
var nodemailer = require('nodemailer');
var url = require('url');
var request = require('request');
var logger = require('../utils/logger.js');
var Utils = require('../utils/util');
var async = require('async');

if(process.env.NODE_ENV === 'test') {
	mailTransport = nodemailer.createTransport(stubTransport());
} else {
	mailTransport = nodemailer.createTransport({
		host: 'mail.stefanocappa.it',
		port: '25',
		debug: true, //this!!!
		auth: {
			user: process.env.USER_EMAIL, //secret data
			pass: process.env.PASS_EMAIL //secret data
		}
	});
}

/**
* @api {post} /api/email Send an email to the administrator.
* @apiVersion 0.0.1
* @apiName PostContact
* @apiGroup Contact
* @apiPermission none
*
* @apiDescription Send an email to the administrator with a text message.
*
* @apiParam {String} response Text with the response received by Google Recaptcha2.
* @apiParam {Object} emailFormData Object with email, object and message texts.
* @apiParam {String} emailFormData.email Text with the sender email.
* @apiParam {String} emailFormData.messageText Text message.
* @apiParam {String} emailFormData.object Text with the email subject (title).
*
* @apiHeaderExample {json} Header-Example:
*     {
*       "Content-Type": "application/json",
*				"XSRF-TOKEN": "A VALID TOKEN"
*     }
*
* @apiSuccess {String} message Text that contains the same emailMessage passed into the request's body.
*
* @apiError RecaptchaError 401 Unknown recaptcha error. It returns recaptcha's response.
* @apiError RecaptchaVerifyError 401 Recaptcha error while verifying. It returns 'Recaptcha verify answered FALSE!'.
* @apiError MissingParamsError 400 It returns 'Missing input params'.
* @apiError EmailSendError 404 It returns null (the value null, not a text).
* @apiError ImpossibleSendError 500 It returns 'Impossibile to send the email'.
* @apiError UnknownError 500 It returns 'Unknown error'.
*
* @apiParamExample {json} Request-Example:
*     {
*       "response": "fsdfsdfsdfsd-response-dasdfasdas",
*       "emailFormData": {
*         "email": "email@emai.it",
*         "messageText": "email message",
*         "object": "email title"
*       }
*     }
*
* @apiSuccessExample {json} Success-Response:
*   HTTP/1.1 200 OK
*   {
*     "message": "email@emai.it"
*   }
*/
module.exports.sendEmailWithRecaptcha = function(req, res) {
	console.log("verifyCaptcha api called in app-api controllers " +req.body.response);

	//complete the request with my secret key
	//as described in google documentation.
	var data = {
		secret: process.env.RECAPTCHA_SECRET,
		response: req.body.response
		//here I can add also the IP, but it's not mandatory
	};

	const RECAPTCHA_URL = 'https://www.google.com/recaptcha/api/siteverify';

	async.waterfall([
    done => {
    	request.post({url:RECAPTCHA_URL, form: data}, (err,response,body) => {
    		console.log(body);
    		done(err, body);
    	});
    },
    (body, done) => {

	    var result = JSON.parse( body );
			console.log(result);
			if(result.success === false) {
				if(result['error-codes']) {
					Utils.sendJSONres(res, 401, result['error-codes']);
				} else {
					Utils.sendJSONres(res, 401, "Recaptcha verify answered FALSE!");
				}
			} else {
				done();
			}
		},
		(done) => {

			console.log("Trying to send an email");
			if (req.body.emailFormData && req.body.emailFormData.email &&
				req.body.emailFormData.object && req.body.emailFormData.messageText) {

				console.log("Preparing to send an email");
				done(null, req.body.emailFormData);
			} else {
				Utils.sendJSONres(res, 400, 'Missing input params');
			}
		},
		(formEmail, done) => {

			console.log('Sending an email from ' + process.env.USER_EMAIL + ' to: ' + formEmail.email);

			var message = {
				from: process.env.USER_EMAIL,
				to: formEmail.email,
				subject: formEmail.object,
				html: formEmail.messageText,
				generateTextFromHtml: true
			};

			mailTransport.sendMail(message, err => {
				if (err) {
					console.log('err ----> returning 404');
					console.log(err.message);
					done(err, 404, null);
				}
				console.log('OK -----> returning 200');
				done(null, 200, formEmail);
			});
		},
		(resultHttpCode, formEmail, done) => {
			console.log("resultHttpCode: " + resultHttpCode);
			if(resultHttpCode === 200) {
				console.log('Message sent successfully to: ' + formEmail.email);
				Utils.sendJSONres(res, 200, { message: formEmail.email });
			} else {
				console.log("Error, resultHttpCode!=200 -> " + resultHttpCode);
				Utils.sendJSONres(res, 500, "Impossibile to send the email");
			}
	  }], (err) => {
	    if (err) {
	    	Utils.sendJSONres(res, 500, "Unknown error");
			}
    });
};
<|endoftext|>


<commit_before>// @flow

import styles from './InterestGroup.css';
import { Image } from 'app/components/Image';
import { Flex } from 'app/components/Layout';
import {
  Content,
  ContentSection,
  ContentMain,
  ContentSidebar,
} from 'app/components/Content';
import UserGrid from 'app/components/UserGrid';
import NavigationTab, { NavigationLink } from 'app/components/NavigationTab';
import Button from 'app/components/Button';
import DisplayContent from 'app/components/DisplayContent';
import AnnouncementInLine from 'app/components/AnnouncementInLine';
import type { Group, User, GroupMembership, ID } from 'app/models';
import InterestGroupMemberList from './InterestGroupMemberList';

type TitleProps = {
  group: Group,
  showEdit: boolean,
};

const Title = ({ group: { name, id }, showEdit }: TitleProps) => (
  <NavigationTab title={name}>
    <NavigationLink to="/interestgroups/">
       Tilbake
    </NavigationLink>
    {showEdit && (
      <NavigationLink to={`/interestgroups/${id}/edit`}>Rediger</NavigationLink>
    )}
  </NavigationTab>
);

type MembersProps = {
  members: Array<GroupMembership>,
  group: Group,
};

const Members = ({ group, members }: MembersProps) => (
  <Flex column>

Medlemmer {group.numberOfUsers}

    <UserGrid
      users={members && members.slice(0, 14).map((reg) => reg.user)}
      maxRows={2}
      minRows={2}
    />
    <InterestGroupMemberList memberships={members}>
      <Flex className={styles.showMemberList}>Vis alle medlemmer</Flex>
    </InterestGroupMemberList>
  </Flex>
);

type ButtonRowProps = {
  group: Group,
  currentUser: User,
  leaveGroup: (GroupMembership, ID) => void,
  joinGroup: (ID, User) => void,
};

const ButtonRow = ({
  group,
  currentUser,
  joinGroup,
  leaveGroup,
}: ButtonRowProps) => {
  const [membership] = group.memberships.filter(
    (m) => m.user.id === currentUser.id
  );

  const onClick = membership
    ? () => leaveGroup(membership, group.id)
    : () => joinGroup(group.id, currentUser);

  return (
    <Flex>
      <Button onClick={onClick}>
        {membership ? 'Forlat gruppen' : 'Bli med i gruppen'}
      </Button>
    </Flex>
  );
};

const Contact = ({ group }: { group: Group }) => {
  const leaders = group.memberships.filter((m) => m.role === 'leader');

  if (leaders.length === 0) {
    return (
      <Flex column>

Leder

        Gruppen har ingen leder!
      </Flex>
    );
  }

  const leader = leaders[0];

  return (
    <Flex column>

Leder

      {leader.user.fullName}
    </Flex>
  );
};

type Props = {
  joinGroup: (ID, User) => void,
  leaveGroup: (GroupMembership) => void,
  currentUser: User,
  group: Group,
};

function InterestGroupDetail(props: Props) {
  const { group } = props;
  const canEdit = group.actionGrant && group.actionGrant.includes('edit');
  const logo = group.logo || 'https://i.imgur.com/Is9VKjb.jpg';
  return (
    <Content>
      <Title group={group} showEdit={canEdit} />
      <ContentSection>
        <ContentMain>

{group.description}

          <DisplayContent content={group.text} />
          <ButtonRow {...props} />
        </ContentMain>
        <ContentSidebar>
          <Image className={styles.logo} src={logo} />
          <Members group={group} members={group.memberships} />
          <Contact group={group} />
          <AnnouncementInLine
            placeholder="Skriv en kunngjøring til alle medlemmer..."
            group={group.id}
            button
          />
        </ContentSidebar>
      </ContentSection>
    </Content>
  );
}

export default InterestGroupDetail;
<commit_msg>show multiple leaders of interest groups<commit_after>// @flow

import styles from './InterestGroup.css';
import { Image } from 'app/components/Image';
import { Flex } from 'app/components/Layout';
import {
  Content,
  ContentSection,
  ContentMain,
  ContentSidebar,
} from 'app/components/Content';
import UserGrid from 'app/components/UserGrid';
import NavigationTab, { NavigationLink } from 'app/components/NavigationTab';
import Button from 'app/components/Button';
import DisplayContent from 'app/components/DisplayContent';
import AnnouncementInLine from 'app/components/AnnouncementInLine';
import type { Group, User, GroupMembership, ID } from 'app/models';
import InterestGroupMemberList from './InterestGroupMemberList';

type TitleProps = {
  group: Group,
  showEdit: boolean,
};

const Title = ({ group: { name, id }, showEdit }: TitleProps) => (
  <NavigationTab title={name}>
    <NavigationLink to="/interestgroups/">
       Tilbake
    </NavigationLink>
    {showEdit && (
      <NavigationLink to={`/interestgroups/${id}/edit`}>Rediger</NavigationLink>
    )}
  </NavigationTab>
);

type MembersProps = {
  members: Array<GroupMembership>,
  group: Group,
};

const Members = ({ group, members }: MembersProps) => (
  <Flex column>

Medlemmer {group.numberOfUsers}

    <UserGrid
      users={members && members.slice(0, 14).map((reg) => reg.user)}
      maxRows={2}
      minRows={2}
    />
    <InterestGroupMemberList memberships={members}>
      <Flex className={styles.showMemberList}>Vis alle medlemmer</Flex>
    </InterestGroupMemberList>
  </Flex>
);

type ButtonRowProps = {
  group: Group,
  currentUser: User,
  leaveGroup: (GroupMembership, ID) => void,
  joinGroup: (ID, User) => void,
};

const ButtonRow = ({
  group,
  currentUser,
  joinGroup,
  leaveGroup,
}: ButtonRowProps) => {
  const [membership] = group.memberships.filter(
    (m) => m.user.id === currentUser.id
  );

  const onClick = membership
    ? () => leaveGroup(membership, group.id)
    : () => joinGroup(group.id, currentUser);

  return (
    <Flex>
      <Button onClick={onClick}>
        {membership ? 'Forlat gruppen' : 'Bli med i gruppen'}
      </Button>
    </Flex>
  );
};

const Contact = ({ group }: { group: Group }) => {
  const leaders = group.memberships.filter((m) => m.role === 'leader');

  if (leaders.length === 0) {
    return (
      <Flex column>

Leder

        Gruppen har ingen leder!
      </Flex>
    );
  }

  if (leaders.length > 1) {
    return (
      <Flex column>

Ledere

          {leaders.map((leader) => (

{leader.user.fullName}

          ))}

      </Flex>
    );
  }

  const leader = leaders[0];
  return (
    <Flex column>

Leder

      {leader.user.fullName}
    </Flex>
  );
};

type Props = {
  joinGroup: (ID, User) => void,
  leaveGroup: (GroupMembership) => void,
  currentUser: User,
  group: Group,
};

function InterestGroupDetail(props: Props) {
  const { group } = props;
  const canEdit = group.actionGrant && group.actionGrant.includes('edit');
  const logo = group.logo || 'https://i.imgur.com/Is9VKjb.jpg';
  return (
    <Content>
      <Title group={group} showEdit={canEdit} />
      <ContentSection>
        <ContentMain>

{group.description}

          <DisplayContent content={group.text} />
          <ButtonRow {...props} />
        </ContentMain>
        <ContentSidebar>
          <Image className={styles.logo} src={logo} />
          <Members group={group} members={group.memberships} />
          <Contact group={group} />
          <AnnouncementInLine
            placeholder="Skriv en kunngjøring til alle medlemmer..."
            group={group.id}
            button
          />
        </ContentSidebar>
      </ContentSection>
    </Content>
  );
}

export default InterestGroupDetail;
<|endoftext|>


<commit_before>/**

    TODO:
    - Init - Done
    - Add scroll event listener - Done
    - Determine whether element is in view - Done
    - Determine whether element is in view minus offset - Done
    - Add animation - Done
    - Kill - Done
    - Throttle scroll event listener - Done
    - Animation delays - Done

 */

(function(root, factory) {
    if ( typeof define === 'function' && define.amd ) {
        define([], factory(root));
    } else if ( typeof exports === 'object' ) {
        module.exports = factory(root);
    } else {
        root.Animate = factory(root);
    }
})(typeof global !== 'undefined' ? global : this.window || this.global, function (root) {

    'use strict';

    var Animate = function(userOptions){
        var defaultOptions = {
            animatedClass: 'js-animated',
            offset: 0.5,
            target: '[data-animate]',
            reverse: false,
            debug: false,
            onLoad: true,
            onScroll: true,
            callback: function(){}
        };

        this.supports = 'querySelector' in document && 'addEventListener' in root;
        this.options = this._extend(defaultOptions, userOptions || {});
        this.elements = root.document.querySelectorAll(this.options.target);
        this.initialised = false;
        // this.init();
    };

    // Returns a function, that, as long as it continues to be invoked, will not
    // be triggered. The function will be called after it stops being called for
    // N milliseconds. If `immediate` is passed, trigger the function on the
    // leading edge, instead of the trailing.
    // @private
    // @author David Walsh
    // @link https://davidwalsh.name/javascript-debounce-function
    Animate.prototype._debounce = function(func, wait, immediate) {
        var timeout;
        return function() {
            var context = this, args = arguments;
            var later = function() {
                timeout = null;
                if (!immediate) {
                    func.apply(context, args);
                }
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) {
                func.apply(context, args);
            }
        };
    };

    /**
     * Merges unspecified amount of objects into new object
     * @private
     * @return {Object} Merged object of arguments
     */
    Animate.prototype._extend = function() {
        var extended = {},
            length = arguments.length;

        var merge = function(obj) {
            for(var prop in obj) {
                extended[prop] = obj[prop];
            }
        };

        for(var i = 0; i < length; i++) {
            var obj = arguments[i];
            if(this._isType('Object', obj)) {
                merge(obj);
            } else {
                console.warn('Custom options must be an object');
            }
        }

        return extended;
    };

    /**
     * Determines when an animation has completed
     * @author  David Walsh
     * @link https://davidwalsh.name/css-animation-callback
     * @private
     * @return {[type]} [description]
     */
    Animate.prototype._whichAnimationEvent = function(){
        var t,
        el = document.createElement("fakeelement");

        var animations = {
            "animation"      : "animationend",
            "OAnimation"     : "oAnimationEnd",
            "MozAnimation"   : "animationend",
            "WebkitAnimation": "webkitAnimationEnd"
        };

        for (t in animations){
            if (el.style[t] !== undefined){
                return animations[t];
            }
        }
    };

    /**
     * Get an the height of the viewport
     * @private
     * @return {Number} Height of viewport
     */
    Animate.prototype._getViewportHeight = function() {
        return Math.max((root.innerHeight || document.documentElement.clientHeight) + root.scrollY);
    };

    /**
     * Get an element's distance from the top of the page
     * @private
     * @param  {Node} elem Element to test for
     * @return {Number} Elements Distance from top of page
     */
    Animate.prototype._getElemDistance = function(el) {
        var location = 0;
        if (el.offsetParent) {
            do {
                location += el.offsetTop;
                el = el.offsetParent;
            } while (el);
        }
        return location >= 0 ? location : 0;
    };

    Animate.prototype._getElementOffset = function(el) {
        return Math.max(el.offsetHeight*this.options.offset);
    };

    /**
     * Determine whether an element is within the viewport
     * @param  {[type]}  el [description]
     * @return {Boolean}    [description]
     */
    Animate.prototype._isInView = function(el) {
        return this._getElemDistance(el) < (this._getViewportHeight() - this._getElementOffset(el))  ? true : false;
    };

    /**
     * Tests whether a DOM node's visibility attribute is set to true
     * @private
     * @param  {Node}  el Element to test
     * @return {Boolean}
     */
    Animate.prototype._isVisible = function(el){
        var visibility = el.getAttribute('data-visibility');
        return true ? visibility === 'true' : '';
    };

    /**
     * Tests whether a DOM node has already been animated
     * @private
     * @param  {Node}  el Element to test
     * @return {Boolean}
     */
    Animate.prototype._hasAnimated = function(el){
        var animated = el.getAttribute('data-animated');
        return true ? animated === 'true' : '';
    };

    /**
     * Test whether an object is of a give type
     * @private
     * @param  {String}  type Type to test for e.g. 'String', 'Array'
     * @param  {Object}  obj  Object to test type against
     * @return {Boolean}      Whether object is of type
     */
    Animate.prototype._isType = function(type, obj) {
        var test = Object.prototype.toString.call(obj).slice(8,-1);
        return obj !== null && obj !== undefined && test === type;
    };

    /**
     * Add animation to given element
     * @private
     * @param {Node} el Element to target
     */
    Animate.prototype._addAnimation = function(el){

        el.setAttribute('data-visibility', true);
        var animations = el.getAttribute('data-animation-classes').split(' ');
        var animationDelay = parseInt(el.getAttribute('data-animation-delay'));

        if(animationDelay && this._isType('Number', animationDelay)) {
            setTimeout(function() {
                if(this.options.debug) console.debug('animate.js: Animation added');
                animations.forEach(function(animation) {
                    el.classList.add(animation);
                });
            }.bind(this), animationDelay);
        } else {
            if(this.options.debug) console.debug('animate.js: Animation added');
            animations.forEach(function(animation){
               el.classList.add(animation);
            });
        }

        this._completeAnimation(el);
    };

    /**
     * Remove animation from given element
     * @private
     * @param {Node} el Element to target
     */
    Animate.prototype._removeAnimation = function(el){

        el.setAttribute('data-visibility', false);
        el.removeAttribute('data-animated');
        var animations = el.getAttribute('data-animation-classes').split(' ');
        var animationDelay = parseInt(el.getAttribute('data-animation-delay'));
        animations.push(this.options.animatedClass);

        if(animationDelay && this._isType('Number', animationDelay)) {
            setTimeout(function() {
                if(this.options.debug) console.debug('animate.js: Animation removed');
                animations.forEach(function(animation) {
                    el.classList.remove(animation);
                });
            }.bind(this), animationDelay);
        } else {
            if(this.options.debug) console.debug('animate.js: Animation removed');
            animations.forEach(function(animation){
               el.classList.remove(animation);
            });
        }

    };

    /**
     * Add class & data attribute to element on animation completion
     * @private
     * @param  {Node} el Element to target
     */
    Animate.prototype._completeAnimation = function(el){
        var animationEvent = this._whichAnimationEvent();
        el.addEventListener(animationEvent, function() {
            if(this.options.debug) console.debug('animate.js: Animation completed');

            el.classList.add(this.options.animatedClass);
            el.setAttribute('data-animated', true);

            if(this._isType('Function', this.options.callback)) {
                this.options.callback();
            }
        }.bind(this));
    };

    /**
     * Initalises event listeners
     * @public
     */
    Animate.prototype.init = function(){
        if(this.options.debug) {
            console.debug('animate.js: Animate.js successfully initialised');
            console.debug('animate.js: Found ' + this.elements.length + ' elements to animate');
        }

        if(!this.supports) return;

        var throttledEvent = this._debounce(function() {
            this.render();
        }.bind(this), 15);

        if(this.options.onLoad) {
            root.addEventListener('DOMContentLoaded', throttledEvent());
        }

        if(this.options.onScroll) {
            root.addEventListener('scroll', function(){
                throttledEvent();
            }, false);
        }

        this.initialised = true;

    };

    /**
     * Stop all running event listeners & resets options to null
     * @public
     */
    Animate.prototype.kill = function(){
        if(this.options.debug) console.debug('animate.js: Animation.js nuked');

        // Test to see whether we have actually initialised
        if (!this.initialised) return;

        // Remove event listeners
        root.removeEventListener('scroll', this.animateListener);
        root.removeEventListener('DOMContentLoaded', this.animateListener);

        // Reset settings
        this.settings = null;
    };

    /**
     * Toggles animations on an event
     * @public
     * @return {}
     */
    Animate.prototype.render = function(){
        var els = this.elements;
        for (var i = els.length - 1; i >= 0; i--) {
            var el = els[i];

            // If element is in view and is not set to visible
            if(this._isInView(el)) {
                if(!this._isVisible(el)){
                    this._addAnimation(el);
                }
            } else if(!this._isInView(el) && this._hasAnimated(el) && this.options.reverse) {
                this._removeAnimation(el);
            }
        }
    };

    /**
     * Get elements that have been animated
     * @public
     * @return {Array} Array of nodes
     */
    Animate.prototype.getAnimatedElements = function() {
        var animatedEls = [];
        for (var i = this.elements.length - 1; i >= 0; i--) {
            var el = this.elements[i];
            if(this._hasAnimated(el)) {
                animatedEls.push(el);
            }
        }
        return animatedEls;
    };

    return Animate;
});

<commit_msg>Pass 'this' to factory<commit_after>/**

    TODO:
    - Init - Done
    - Add scroll event listener - Done
    - Determine whether element is in view - Done
    - Determine whether element is in view minus offset - Done
    - Add animation - Done
    - Kill - Done
    - Throttle scroll event listener - Done
    - Animation delays - Done

 */

 (function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(function() {
            return factory(root);
        });
    } else if (typeof exports === 'object') {
        module.exports = factory;
    } else {
        root.Animate = factory(root);
    }
 })(this, function (root) {

    'use strict';

    var Animate = function(userOptions){
        var defaultOptions = {
            animatedClass: 'js-animated',
            offset: 0.5,
            target: '[data-animate]',
            reverse: false,
            debug: false,
            onLoad: true,
            onScroll: true,
            callback: function(){}
        };

        this.supports = 'querySelector' in document && 'addEventListener' in root;
        this.options = this._extend(defaultOptions, userOptions || {});
        this.elements = root.document.querySelectorAll(this.options.target);
        this.initialised = false;
        // this.init();
    };

    // Returns a function, that, as long as it continues to be invoked, will not
    // be triggered. The function will be called after it stops being called for
    // N milliseconds. If `immediate` is passed, trigger the function on the
    // leading edge, instead of the trailing.
    // @private
    // @author David Walsh
    // @link https://davidwalsh.name/javascript-debounce-function
    Animate.prototype._debounce = function(func, wait, immediate) {
        var timeout;
        return function() {
            var context = this, args = arguments;
            var later = function() {
                timeout = null;
                if (!immediate) {
                    func.apply(context, args);
                }
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) {
                func.apply(context, args);
            }
        };
    };

    /**
     * Merges unspecified amount of objects into new object
     * @private
     * @return {Object} Merged object of arguments
     */
    Animate.prototype._extend = function() {
        var extended = {},
            length = arguments.length;

        var merge = function(obj) {
            for(var prop in obj) {
                extended[prop] = obj[prop];
            }
        };

        for(var i = 0; i < length; i++) {
            var obj = arguments[i];
            if(this._isType('Object', obj)) {
                merge(obj);
            } else {
                console.warn('Custom options must be an object');
            }
        }

        return extended;
    };

    /**
     * Determines when an animation has completed
     * @author  David Walsh
     * @link https://davidwalsh.name/css-animation-callback
     * @private
     * @return {[type]} [description]
     */
    Animate.prototype._whichAnimationEvent = function(){
        var t,
        el = document.createElement("fakeelement");

        var animations = {
            "animation"      : "animationend",
            "OAnimation"     : "oAnimationEnd",
            "MozAnimation"   : "animationend",
            "WebkitAnimation": "webkitAnimationEnd"
        };

        for (t in animations){
            if (el.style[t] !== undefined){
                return animations[t];
            }
        }
    };

    /**
     * Get an the height of the viewport
     * @private
     * @return {Number} Height of viewport
     */
    Animate.prototype._getViewportHeight = function() {
        return Math.max((root.innerHeight || document.documentElement.clientHeight) + root.scrollY);
    };

    /**
     * Get an element's distance from the top of the page
     * @private
     * @param  {Node} elem Element to test for
     * @return {Number} Elements Distance from top of page
     */
    Animate.prototype._getElemDistance = function(el) {
        var location = 0;
        if (el.offsetParent) {
            do {
                location += el.offsetTop;
                el = el.offsetParent;
            } while (el);
        }
        return location >= 0 ? location : 0;
    };

    Animate.prototype._getElementOffset = function(el) {
        return Math.max(el.offsetHeight*this.options.offset);
    };

    /**
     * Determine whether an element is within the viewport
     * @param  {[type]}  el [description]
     * @return {Boolean}    [description]
     */
    Animate.prototype._isInView = function(el) {
        return this._getElemDistance(el) < (this._getViewportHeight() - this._getElementOffset(el))  ? true : false;
    };

    /**
     * Tests whether a DOM node's visibility attribute is set to true
     * @private
     * @param  {Node}  el Element to test
     * @return {Boolean}
     */
    Animate.prototype._isVisible = function(el){
        var visibility = el.getAttribute('data-visibility');
        return true ? visibility === 'true' : '';
    };

    /**
     * Tests whether a DOM node has already been animated
     * @private
     * @param  {Node}  el Element to test
     * @return {Boolean}
     */
    Animate.prototype._hasAnimated = function(el){
        var animated = el.getAttribute('data-animated');
        return true ? animated === 'true' : '';
    };

    /**
     * Test whether an object is of a give type
     * @private
     * @param  {String}  type Type to test for e.g. 'String', 'Array'
     * @param  {Object}  obj  Object to test type against
     * @return {Boolean}      Whether object is of type
     */
    Animate.prototype._isType = function(type, obj) {
        var test = Object.prototype.toString.call(obj).slice(8,-1);
        return obj !== null && obj !== undefined && test === type;
    };

    /**
     * Add animation to given element
     * @private
     * @param {Node} el Element to target
     */
    Animate.prototype._addAnimation = function(el){

        el.setAttribute('data-visibility', true);
        var animations = el.getAttribute('data-animation-classes').split(' ');
        var animationDelay = parseInt(el.getAttribute('data-animation-delay'));

        if(animationDelay && this._isType('Number', animationDelay)) {
            setTimeout(function() {
                if(this.options.debug) console.debug('animate.js: Animation added');
                animations.forEach(function(animation) {
                    el.classList.add(animation);
                });
            }.bind(this), animationDelay);
        } else {
            if(this.options.debug) console.debug('animate.js: Animation added');
            animations.forEach(function(animation){
               el.classList.add(animation);
            });
        }

        this._completeAnimation(el);
    };

    /**
     * Remove animation from given element
     * @private
     * @param {Node} el Element to target
     */
    Animate.prototype._removeAnimation = function(el){

        el.setAttribute('data-visibility', false);
        el.removeAttribute('data-animated');
        var animations = el.getAttribute('data-animation-classes').split(' ');
        var animationDelay = parseInt(el.getAttribute('data-animation-delay'));
        animations.push(this.options.animatedClass);

        if(animationDelay && this._isType('Number', animationDelay)) {
            setTimeout(function() {
                if(this.options.debug) console.debug('animate.js: Animation removed');
                animations.forEach(function(animation) {
                    el.classList.remove(animation);
                });
            }.bind(this), animationDelay);
        } else {
            if(this.options.debug) console.debug('animate.js: Animation removed');
            animations.forEach(function(animation){
               el.classList.remove(animation);
            });
        }

    };

    /**
     * Add class & data attribute to element on animation completion
     * @private
     * @param  {Node} el Element to target
     */
    Animate.prototype._completeAnimation = function(el){
        var animationEvent = this._whichAnimationEvent();
        el.addEventListener(animationEvent, function() {
            if(this.options.debug) console.debug('animate.js: Animation completed');

            el.classList.add(this.options.animatedClass);
            el.setAttribute('data-animated', true);

            if(this._isType('Function', this.options.callback)) {
                this.options.callback();
            }
        }.bind(this));
    };

    /**
     * Initalises event listeners
     * @public
     */
    Animate.prototype.init = function(){
        if(this.options.debug) {
            console.debug('animate.js: Animate.js successfully initialised');
            console.debug('animate.js: Found ' + this.elements.length + ' elements to animate');
        }

        if(!this.supports) return;

        var throttledEvent = this._debounce(function() {
            this.render();
        }.bind(this), 15);

        if(this.options.onLoad) {
            root.addEventListener('DOMContentLoaded', throttledEvent());
        }

        if(this.options.onScroll) {
            root.addEventListener('scroll', function(){
                throttledEvent();
            }, false);
        }

        this.initialised = true;

    };

    /**
     * Stop all running event listeners & resets options to null
     * @public
     */
    Animate.prototype.kill = function(){
        if(this.options.debug) console.debug('animate.js: Animation.js nuked');

        // Test to see whether we have actually initialised
        if (!this.initialised) return;

        // Remove event listeners
        root.removeEventListener('scroll', this.animateListener);
        root.removeEventListener('DOMContentLoaded', this.animateListener);

        // Reset settings
        this.settings = null;
    };

    /**
     * Toggles animations on an event
     * @public
     * @return {}
     */
    Animate.prototype.render = function(){
        var els = this.elements;
        for (var i = els.length - 1; i >= 0; i--) {
            var el = els[i];

            // If element is in view and is not set to visible
            if(this._isInView(el)) {
                if(!this._isVisible(el)){
                    this._addAnimation(el);
                }
            } else if(!this._isInView(el) && this._hasAnimated(el) && this.options.reverse) {
                this._removeAnimation(el);
            }
        }
    };

    /**
     * Get elements that have been animated
     * @public
     * @return {Array} Array of nodes
     */
    Animate.prototype.getAnimatedElements = function() {
        var animatedEls = [];
        for (var i = this.elements.length - 1; i >= 0; i--) {
            var el = this.elements[i];
            if(this._hasAnimated(el)) {
                animatedEls.push(el);
            }
        }
        return animatedEls;
    };

    return Animate;
});
<|endoftext|>


<commit_before>/*
 * This work is licensed under the Creative Commons Attribution-NonCommercial 4.0 International License.
 * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/4.0/.
 * Copyright (c) 2016 Ivan Aracki
 */

window.onload = function() {

  var messagesEl = document.querySelector('.messages');
  var typingSpeed = 20;
  var loadingText = '•••';
  var messageIndex = 0;

  var getCurrentTime = function() {
    var date = new Date();
    var hours =  date.getHours();
    var minutes =  date.getMinutes();
    var current = hours + (minutes * .01);
    if (current >= 5 && current < 19) return 'Have a nice day';
    if (current >= 19 && current < 22) return 'Have a nice evening';
    if (current >= 22 || current < 5) return 'Have a good night';
  };

  var messages = [
    'Hey there 👋',
    'I\'m software developer 💻 from Belgrade 🇷🇸',
    'You can contact me at aracki.ivan@gmail.com 💌',
    getCurrentTime(),
    '⛵ I.A.'
  ];

  var getFontSize = function() {
    return parseInt(getComputedStyle(document.body).getPropertyValue('font-size'));
  };

  var pxToRem = function(px) {
    return px / getFontSize() + 'rem';
  };

  var createBubbleElements = function(message, position) {
    var bubbleEl = document.createElement('div');
    var messageEl = document.createElement('span');
    var loadingEl = document.createElement('span');
    bubbleEl.classList.add('bubble');
    bubbleEl.classList.add('is-loading');
    bubbleEl.classList.add('cornered');
    bubbleEl.classList.add(position === 'right' ? 'right' : 'left');
    messageEl.classList.add('message');
    loadingEl.classList.add('loading');
    messageEl.innerHTML = message;
    loadingEl.innerHTML = loadingText;
    bubbleEl.appendChild(loadingEl);
    bubbleEl.appendChild(messageEl);
    bubbleEl.style.opacity = 0;
    return {
      bubble: bubbleEl,
      message: messageEl,
      loading: loadingEl
    }
  };

  var getDimentions = function(elements) {
    return dimensions = {
      loading: {
        w: '4rem',
        h: '2.25rem'
      },
      bubble: {
        w: pxToRem(elements.bubble.offsetWidth + 4),
        h: pxToRem(elements.bubble.offsetHeight)
      },
      message: {
        w: pxToRem(elements.message.offsetWidth + 4),
        h: pxToRem(elements.message.offsetHeight)
      }
    }
  };

  var sendMessage = function(message, position) {
    var loadingDuration = (message.replace(/<(?:.|\n)*?>/gm, '').length * typingSpeed) + 500;
    var elements = createBubbleElements(message, position);
    messagesEl.appendChild(elements.bubble);
    messagesEl.appendChild(document.createElement('br'));
    var dimensions = getDimentions(elements);
    elements.bubble.style.width = '0rem';
    elements.bubble.style.height = dimensions.loading.h;
    elements.message.style.width = dimensions.message.w;
    elements.message.style.height = dimensions.message.h;
    elements.bubble.style.opacity = 1;
    var bubbleOffset = elements.bubble.offsetTop + elements.bubble.offsetHeight;
    if (bubbleOffset > messagesEl.offsetHeight) {
      var scrollMessages = anime({
        targets: messagesEl,
        scrollTop: bubbleOffset,
        duration: 750
      });
    }
    var bubbleSize = anime({
      targets: elements.bubble,
      width: ['0rem', dimensions.loading.w],
      marginTop: ['2.5rem', 0],
      marginLeft: ['-2.5rem', 0],
      duration: 800,
      easing: 'easeOutElastic'
    });
    var loadingLoop = anime({
      targets: elements.bubble,
      scale: [1.05, .95],
      duration: 1100,
      loop: true,
      direction: 'alternate',
      easing: 'easeInOutQuad'
    });
    var dotsStart = anime({
      targets: elements.loading,
      translateX: ['-2rem', '0rem'],
      scale: [.5, 1],
      duration: 400,
      delay: 25,
      easing: 'easeOutElastic',
    });
    var dotsPulse = anime({
      targets: elements.bubble.querySelectorAll('b'),
      scale: [1, 1.25],
      opacity: [.5, 1],
      duration: 300,
      loop: true,
      direction: 'alternate',
      delay: function(i) {return (i * 100) + 50}
    });
    setTimeout(function() {
      loadingLoop.pause();
      dotsPulse.restart({
        opacity: 0,
        scale: 0,
        loop: false,
        direction: 'forwards',
        update: function(a) {
          if (a.progress >= 65 && elements.bubble.classList.contains('is-loading')) {
            elements.bubble.classList.remove('is-loading');
            anime({
              targets: elements.message,
              opacity: [0, 1],
              duration: 300,
            });
          }
        }
      });
      bubbleSize.restart({
        scale: 1,
        width: [dimensions.loading.w, dimensions.bubble.w ],
        height: [dimensions.loading.h, dimensions.bubble.h ],
        marginTop: 0,
        marginLeft: 0,
        begin: function() {
          if (messageIndex < messages.length) elements.bubble.classList.remove('cornered');
        }
      })
    }, loadingDuration - 50);
  };

  var sendMessages = function() {
    var message = messages[messageIndex];
    if (!message) return;
    sendMessage(message);
    ++messageIndex;
    setTimeout(sendMessages, (message.replace(/<(?:.|\n)*?>/gm, '').length * typingSpeed) + anime.random(900, 1200));
  };

  sendMessages();

};
<commit_msg>Remove bubble messages emoji<commit_after>/*
 * This work is licensed under the Creative Commons Attribution-NonCommercial 4.0 International License.
 * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/4.0/.
 * Copyright (c) 2016 Ivan Aracki
 */

window.onload = function() {

  var messagesEl = document.querySelector('.messages');
  var typingSpeed = 20;
  var loadingText = '•••';
  var messageIndex = 0;

  var getCurrentTime = function() {
    var date = new Date();
    var hours =  date.getHours();
    var minutes =  date.getMinutes();
    var current = hours + (minutes * .01);
    if (current >= 5 && current < 19) return 'Have a nice day';
    if (current >= 19 && current < 22) return 'Have a nice evening';
    if (current >= 22 || current < 5) return 'Have a good night';
  };

  var messages = [
    'Hey there 👋',
    'I\'m software developer from Belgrade',
    'You can contact me at aracki.ivan@gmail.com',
    getCurrentTime(),
    '⛵ I.A.'
  ];

  var getFontSize = function() {
    return parseInt(getComputedStyle(document.body).getPropertyValue('font-size'));
  };

  var pxToRem = function(px) {
    return px / getFontSize() + 'rem';
  };

  var createBubbleElements = function(message, position) {
    var bubbleEl = document.createElement('div');
    var messageEl = document.createElement('span');
    var loadingEl = document.createElement('span');
    bubbleEl.classList.add('bubble');
    bubbleEl.classList.add('is-loading');
    bubbleEl.classList.add('cornered');
    bubbleEl.classList.add(position === 'right' ? 'right' : 'left');
    messageEl.classList.add('message');
    loadingEl.classList.add('loading');
    messageEl.innerHTML = message;
    loadingEl.innerHTML = loadingText;
    bubbleEl.appendChild(loadingEl);
    bubbleEl.appendChild(messageEl);
    bubbleEl.style.opacity = 0;
    return {
      bubble: bubbleEl,
      message: messageEl,
      loading: loadingEl
    }
  };

  var getDimentions = function(elements) {
    return dimensions = {
      loading: {
        w: '4rem',
        h: '2.25rem'
      },
      bubble: {
        w: pxToRem(elements.bubble.offsetWidth + 4),
        h: pxToRem(elements.bubble.offsetHeight)
      },
      message: {
        w: pxToRem(elements.message.offsetWidth + 4),
        h: pxToRem(elements.message.offsetHeight)
      }
    }
  };

  var sendMessage = function(message, position) {
    var loadingDuration = (message.replace(/<(?:.|\n)*?>/gm, '').length * typingSpeed) + 500;
    var elements = createBubbleElements(message, position);
    messagesEl.appendChild(elements.bubble);
    messagesEl.appendChild(document.createElement('br'));
    var dimensions = getDimentions(elements);
    elements.bubble.style.width = '0rem';
    elements.bubble.style.height = dimensions.loading.h;
    elements.message.style.width = dimensions.message.w;
    elements.message.style.height = dimensions.message.h;
    elements.bubble.style.opacity = 1;
    var bubbleOffset = elements.bubble.offsetTop + elements.bubble.offsetHeight;
    if (bubbleOffset > messagesEl.offsetHeight) {
      var scrollMessages = anime({
        targets: messagesEl,
        scrollTop: bubbleOffset,
        duration: 750
      });
    }
    var bubbleSize = anime({
      targets: elements.bubble,
      width: ['0rem', dimensions.loading.w],
      marginTop: ['2.5rem', 0],
      marginLeft: ['-2.5rem', 0],
      duration: 800,
      easing: 'easeOutElastic'
    });
    var loadingLoop = anime({
      targets: elements.bubble,
      scale: [1.05, .95],
      duration: 1100,
      loop: true,
      direction: 'alternate',
      easing: 'easeInOutQuad'
    });
    var dotsStart = anime({
      targets: elements.loading,
      translateX: ['-2rem', '0rem'],
      scale: [.5, 1],
      duration: 400,
      delay: 25,
      easing: 'easeOutElastic',
    });
    var dotsPulse = anime({
      targets: elements.bubble.querySelectorAll('b'),
      scale: [1, 1.25],
      opacity: [.5, 1],
      duration: 300,
      loop: true,
      direction: 'alternate',
      delay: function(i) {return (i * 100) + 50}
    });
    setTimeout(function() {
      loadingLoop.pause();
      dotsPulse.restart({
        opacity: 0,
        scale: 0,
        loop: false,
        direction: 'forwards',
        update: function(a) {
          if (a.progress >= 65 && elements.bubble.classList.contains('is-loading')) {
            elements.bubble.classList.remove('is-loading');
            anime({
              targets: elements.message,
              opacity: [0, 1],
              duration: 300,
            });
          }
        }
      });
      bubbleSize.restart({
        scale: 1,
        width: [dimensions.loading.w, dimensions.bubble.w ],
        height: [dimensions.loading.h, dimensions.bubble.h ],
        marginTop: 0,
        marginLeft: 0,
        begin: function() {
          if (messageIndex < messages.length) elements.bubble.classList.remove('cornered');
        }
      })
    }, loadingDuration - 50);
  };

  var sendMessages = function() {
    var message = messages[messageIndex];
    if (!message) return;
    sendMessage(message);
    ++messageIndex;
    setTimeout(sendMessages, (message.replace(/<(?:.|\n)*?>/gm, '').length * typingSpeed) + anime.random(900, 1200));
  };

  sendMessages();

};
<|endoftext|>