<commit_before>define(['app', 'angular', 'lodash', 'authentication'], function(app, angular, _) { 'use strict';

app.factory('IStorage', ["$http", "$q", "authentication", "realm", function($http, $q, authentication, defaultRealm) {

	function IStorage()
	{
		var serviceUrls = { // Add Https if not .local
			documentQueryUrl   : function() { return "/api/v2013/documents/"; },
			documentUrl        : function() { return "/api/v2013/documents/:identifier"; },
			validateUrl        : function() { return "/api/v2013/documents/x/validate"; },
			draftUrl           : function() { return "/api/v2013/documents/:identifier/versions/draft"; },
			attachmentUrl      : function() { return "/api/v2013/documents/:identifier/attachments/:filename"; },
			securityUrl        : function() { return "/api/v2013/documents/:identifier/securities/:operation"; },
			draftSecurityUrl   : function() { return "/api/v2013/documents/:identifier/versions/draft/securities/:operation"; },
			draftLockUrl       : function() { return "/api/v2013/documents/:identifier/versions/draft/locks/:lockID"; },
			documentFacetsQueryUrl : function() { return "/api/v2013/documents/query/facets"; },
		};

		//==================================================
		//
		// Documents
		//
		//==================================================
		this.documents = {

			//===========================
			//
			//===========================
			"query" : function(query, collection, params)
			{
				params            = _.extend({}, params||{});
				params.collection = collection;
				params.$filter    = query;

				if (query && !collection)
					params.collection = "my";

				var useCache = !!params.cache;

				params.cache = undefined;

				var oTrans = transformPath(serviceUrls.documentQueryUrl(), params);

				return $http.get(oTrans.url, {  params : oTrans.params, cache:useCache });

				//TODO: return result.data;
			},

			//===========================
			//
			//===========================
			"get" : function(identifier, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				var useCache = !!params.cache;

				params.cache = undefined;

				var oTrans = transformPath(serviceUrls.documentUrl(), params);

				return $http.get(oTrans.url, { params : oTrans.params, cache:useCache });

				//TODO: return result.data;

			},

			//===========================
			//
			//===========================
			"exists" : function(identifier, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				var useCache = !!params.cache;

				params.cache = undefined;

				var oTrans = transformPath(serviceUrls.documentUrl(), params);

				return $http.head(oTrans.url, { params : oTrans.params, cache:useCache }).then(function() {

					return true;

				}).catch(function(error) {

					if(error.status!="404")
						throw "Error";

					return false;
				});
			},

			//===========================
			//
			//===========================
			"put" : function(identifier, data, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				if (!params.schema && data && data.header && data.header.schema)
					params.schema = data.header.schema;

				var oTrans = transformPath(serviceUrls.documentUrl(), params);

				return $http.put(oTrans.url, data, { "params" : oTrans.params }).then(function(result){
					return result.data;
				});
			},

			//===========================
			//
			//===========================
			"delete" : function(identifier, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				var oTrans = transformPath(serviceUrls.documentUrl(), params);

				return $http.delete(oTrans.url, { "params" : oTrans.params });
			},

			//===========================
			//
			//===========================
			"validate" : function(document, params)
			{
				params = clone(params || {});

				if (!params.schema && document && document.header && document.header.schema)
					params.schema = document.header.schema;

				var oTrans = transformPath(serviceUrls.validateUrl(), params);

				return $http.put(oTrans.url, document, { "params" : oTrans.params });

				//TODO: return result.data;
			},

			//===========================
			//
			//===========================
			"security": {
				canCreate: function(identifier, schema, metadata) {
					return canDo(serviceUrls.securityUrl(), "create", identifier, schema, metadata);
				},

				canUpdate: function(identifier, schema, metadata) {
					return canDo(serviceUrls.securityUrl(), "update", identifier, schema, metadata);
				},

				canDelete: function(identifier, schema, metadata) {
					return canDo(serviceUrls.securityUrl(), "delete", identifier, schema, metadata);
				}
			}
		};

		//==================================================
		//
		// Drafts
		//
		//==================================================
		this.drafts = {

			//===========================
			//
			//===========================
			"query" : function(query, params)
			{
				params            = clone(params||{});
				params.collection = "mydraft";
				params.$filter    = query;

				var useCache = !!params.cache;

				params.cache = undefined;

				var oTrans = transformPath(serviceUrls.documentQueryUrl(), params);

				return $http.get(oTrans.url, {  params : oTrans.params, cache:useCache });

				//TODO: return result.data;
			},

			//===========================
			//
			//===========================
			"get" : function(identifier, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				var useCache = !!params.cache;

				params.cache = undefined;

				var oTrans = transformPath(serviceUrls.draftUrl(), params);

				return $http.get(oTrans.url, {  params : oTrans.params, cache:useCache });

				//TODO: return result.data;
			},

			//===========================
			//
			//===========================
			"exists" : function(identifier, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				var useCache = !!params.cache;

				params.cache = undefined;

				var oTrans = transformPath(serviceUrls.draftUrl(), params);

				return $http.head(oTrans.url, {  params : oTrans.params, cache:useCache }).then(function() {

					return true;

				}).catch(function(error) {

					if(error.status!="404")
						throw "Error";

					return false;
				});
			},

			//===========================
			//
			//===========================
			"put" : function(identifier, data, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				if (!params.schema && data && data.header && data.header.schema)
					params.schema = data.header.schema;

				var oTrans = transformPath(serviceUrls.draftUrl(), params);

				return $http.put(oTrans.url, data, { "params" : oTrans.params }).then(function(result){
					return result.data;
				});
			},

			//===========================
			//
			//===========================
			"delete" : function(identifier, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				var oTrans = transformPath(serviceUrls.draftUrl(), params);

				return $http.delete(oTrans.url, { "params" : oTrans.params });

				//TODO: return result.data;
			},

			//===========================
			//
			//===========================
			"security": {
				canCreate: function(identifier, schema, metadata) {
					return canDo(serviceUrls.draftSecurityUrl(), "create", identifier, schema, metadata);
				},

				canUpdate: function(identifier, schema, metadata) {
					return canDo(serviceUrls.draftSecurityUrl(), "update", identifier, schema, metadata);
				},

				canDelete: function(identifier, schema, metadata) {
					return canDo(serviceUrls.draftSecurityUrl(), "delete", identifier, schema, metadata);
				}
			},

			"locks" : {

				//===========================
				//
				// Not tested
				//
				//===========================
				"delete" : function(identifier, lockID)
				{
					var params = {
						identifier : identifier,
						lockID     : lockID
					};

					var oTrans = transformPath(serviceUrls.draftLockUrl(), params);

					return $http.delete(oTrans.url).then(function(success) {
						return success.data;
					});
				}
			}
		};

		this.attachments = {

			//===========================
			//
			// Not tested
			//
			//===========================
			"put" : function(identifier, file, params)
			{
				params            = params || {};
				params.identifier = identifier;
				params.filename   = file.name;

				var contentType = params.contentType || getMimeTypes(file.name, file.type || "application/octet-stream");

				params.contentType = undefined;

				var oTrans = transformPath(serviceUrls.attachmentUrl(), params);

				return $http.put(oTrans.url, file, {
					"headers" : { "Content-Type": contentType },
					"params"  : oTrans.params
				}).then(
				function(success) {
					return angular.extend(success.data || {}, { "url": oTrans.url });
				},
				function(error) {
					error.data = angular.extend(error.data || {}, { "url": oTrans.url });
					return error;
				});
			},

			"getMimeType": function(file)
			{
				return getMimeTypes(file.name, file.type || "application/octet-stream");
			}
		};

		//==================================================
		//
		// Document Query
		//
		//==================================================
		this.documentQuery = {

			//===========================
			//
			//===========================
			"facets" : function(filter, params)
			{
			params            = _.extend({}, params||{});
			params.$filter    = filter;

			var useCache = !!params.cache;
			if(!params.cache)
				params.cache = true;

			var oTrans = transformPath(serviceUrls.documentFacetsQueryUrl(), params);

			return $http.get(oTrans.url, {  params : oTrans.params, cache:useCache });

			//TODO: return result.data;
			}

		};

		//==================================================
		//
		//
		//==================================================
		var getMimeTypes = function(filename, defaultMimeType) {
			var sMimeType = defaultMimeType || "application/octet-stream";

			if (filename && sMimeType == "application/octet-stream")
			{
				var sExt   = "";
				var iIndex = filename.lastIndexOf(".");

				if (iIndex >= 0)
					sExt = filename.substring(iIndex).toLowerCase();

				if (sExt == ".json")    sMimeType = "application/json";
				if (sExt == ".geojson") sMimeType = "application/json";
				if (sExt == ".xml")     sMimeType = "application/xml";
			}

			return sMimeType;
		};

		//==================================================
		//
		//
		//==================================================
		var clone = function(obj) {

			if (obj === null)
				return null;

			if (obj === undefined)
				return undefined;

			return angular.fromJson(angular.toJson(obj));
		};

		//===========================
		//
		// Replace :xyz in path with value in params
		// query part will be done by $http
		//
		//===========================
		var transformPath = function(url, params)
		{
			var oRegex     = /\:\w+/g;
			var oMatch     = null;
			var qMatches   = [];
			var oNewParams = {};

			while ((oMatch = oRegex.exec(url)) != null) {
				oMatch.key   = oMatch[0].substring(1);
				oMatch.value = oMatch[0];
				qMatches.splice(0, 0, oMatch);
			}

			for(var key in params||{}) {
				var bExist = false;

				for(var k in qMatches) {
					if (qMatches[k].key != key)
						continue;

					bExist = true;
					qMatches[k].value = params[key].toString();
				}

				if(!bExist)
					oNewParams[key] = params[key];
			}

			for (var j in qMatches) {
				url = replaceAt(url, qMatches[j].index, qMatches[j][0].length, encodeURIComponent(qMatches[j].value));
			}

			return { "url" : url, "params" : oNewParams };
		};

		//===========================
		//
		// Calls storage security
		//
		//===========================
		var canDo = function(patternPath, operation, identifier, schema, metadata) {

			metadata = angular.extend({}, { 'schema': schema }, metadata || {});

			if (!metadata.government && authentication.user().government)
				metadata = angular.extend(metadata, { 'government': authentication.user().government });

			if (!metadata.realm && defaultRealm)
				metadata = angular.extend(metadata, { 'realm': defaultRealm });

			var params = {
				"identifier" : identifier,
				"operation"  : operation,
				"metadata"   : metadata
			};

			var oTrans = transformPath(patternPath, params);

			return $http.get(oTrans.url, { "params": oTrans.params }).then(
				function(res) {
					return res.data.isAllowed;
				});
		};

		//===========================
		//
		// Replace :xyz in path with value in params
		// query part will be done by $http
		//
		//===========================
		var replaceAt = function(str, index, len, newText) {
			return str.substring(0, index) + newText + str.substring(index+len);
		};

		return this;
	}

    return new IStorage();
}]);
});
<commit_msg>add various methods for draft locks<commit_after>define(['app', 'angular', 'lodash', 'authentication'], function(app, angular, _) { 'use strict';

app.factory('IStorage', ["$http", "$q", "authentication", "realm", function($http, $q, authentication, defaultRealm) {

	function IStorage()
	{
		var serviceUrls = { // Add Https if not .local
			documentQueryUrl   : function() { return "/api/v2013/documents/"; },
			documentUrl        : function() { return "/api/v2013/documents/:identifier"; },
			validateUrl        : function() { return "/api/v2013/documents/x/validate"; },
			draftUrl           : function() { return "/api/v2013/documents/:identifier/versions/draft"; },
			attachmentUrl      : function() { return "/api/v2013/documents/:identifier/attachments/:filename"; },
			securityUrl        : function() { return "/api/v2013/documents/:identifier/securities/:operation"; },
			draftSecurityUrl   : function() { return "/api/v2013/documents/:identifier/versions/draft/securities/:operation"; },
			draftLockUrl       : function() { return "/api/v2013/documents/:identifier/versions/draft/locks/:lockID"; },
			documentFacetsQueryUrl : function() { return "/api/v2013/documents/query/facets"; },
		};

		//==================================================
		//
		// Documents
		//
		//==================================================
		this.documents = {

			//===========================
			//
			//===========================
			"query" : function(query, collection, params)
			{
				params            = _.extend({}, params||{});
				params.collection = collection;
				params.$filter    = query;

				if (query && !collection)
					params.collection = "my";

				var useCache = !!params.cache;

				params.cache = undefined;

				var oTrans = transformPath(serviceUrls.documentQueryUrl(), params);

				return $http.get(oTrans.url, {  params : oTrans.params, cache:useCache });

				//TODO: return result.data;
			},

			//===========================
			//
			//===========================
			"get" : function(identifier, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				var useCache = !!params.cache;

				params.cache = undefined;

				var oTrans = transformPath(serviceUrls.documentUrl(), params);

				return $http.get(oTrans.url, { params : oTrans.params, cache:useCache });

				//TODO: return result.data;

			},

			//===========================
			//
			//===========================
			"exists" : function(identifier, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				var useCache = !!params.cache;

				params.cache = undefined;

				var oTrans = transformPath(serviceUrls.documentUrl(), params);

				return $http.head(oTrans.url, { params : oTrans.params, cache:useCache }).then(function() {

					return true;

				}).catch(function(error) {

					if(error.status!="404")
						throw "Error";

					return false;
				});
			},

			//===========================
			//
			//===========================
			"put" : function(identifier, data, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				if (!params.schema && data && data.header && data.header.schema)
					params.schema = data.header.schema;

				var oTrans = transformPath(serviceUrls.documentUrl(), params);

				return $http.put(oTrans.url, data, { "params" : oTrans.params }).then(function(result){
					return result.data;
				});
			},

			//===========================
			//
			//===========================
			"delete" : function(identifier, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				var oTrans = transformPath(serviceUrls.documentUrl(), params);

				return $http.delete(oTrans.url, { "params" : oTrans.params });
			},

			//===========================
			//
			//===========================
			"validate" : function(document, params)
			{
				params = clone(params || {});

				if (!params.schema && document && document.header && document.header.schema)
					params.schema = document.header.schema;

				var oTrans = transformPath(serviceUrls.validateUrl(), params);

				return $http.put(oTrans.url, document, { "params" : oTrans.params });

				//TODO: return result.data;
			},

			//===========================
			//
			//===========================
			"security": {
				canCreate: function(identifier, schema, metadata) {
					return canDo(serviceUrls.securityUrl(), "create", identifier, schema, metadata);
				},

				canUpdate: function(identifier, schema, metadata) {
					return canDo(serviceUrls.securityUrl(), "update", identifier, schema, metadata);
				},

				canDelete: function(identifier, schema, metadata) {
					return canDo(serviceUrls.securityUrl(), "delete", identifier, schema, metadata);
				}
			}
		};

		//==================================================
		//
		// Drafts
		//
		//==================================================
		this.drafts = {

			//===========================
			//
			//===========================
			"query" : function(query, params)
			{
				params            = clone(params||{});
				params.collection = "mydraft";
				params.$filter    = query;

				var useCache = !!params.cache;

				params.cache = undefined;

				var oTrans = transformPath(serviceUrls.documentQueryUrl(), params);

				return $http.get(oTrans.url, {  params : oTrans.params, cache:useCache });

				//TODO: return result.data;
			},

			//===========================
			//
			//===========================
			"get" : function(identifier, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				var useCache = !!params.cache;

				params.cache = undefined;

				var oTrans = transformPath(serviceUrls.draftUrl(), params);

				return $http.get(oTrans.url, {  params : oTrans.params, cache:useCache });

				//TODO: return result.data;
			},

			//===========================
			//
			//===========================
			"exists" : function(identifier, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				var useCache = !!params.cache;

				params.cache = undefined;

				var oTrans = transformPath(serviceUrls.draftUrl(), params);

				return $http.head(oTrans.url, {  params : oTrans.params, cache:useCache }).then(function() {

					return true;

				}).catch(function(error) {

					if(error.status!="404")
						throw "Error";

					return false;
				});
			},

			//===========================
			//
			//===========================
			"put" : function(identifier, data, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				if (!params.schema && data && data.header && data.header.schema)
					params.schema = data.header.schema;

				var oTrans = transformPath(serviceUrls.draftUrl(), params);

				return $http.put(oTrans.url, data, { "params" : oTrans.params }).then(function(result){
					return result.data;
				});
			},

			//===========================
			//
			//===========================
			"delete" : function(identifier, params)
			{
				params            = clone(params||{});
				params.identifier = identifier;

				var oTrans = transformPath(serviceUrls.draftUrl(), params);

				return $http.delete(oTrans.url, { "params" : oTrans.params });

				//TODO: return result.data;
			},

			//===========================
			//
			//===========================
			"security": {
				canCreate: function(identifier, schema, metadata) {
					return canDo(serviceUrls.draftSecurityUrl(), "create", identifier, schema, metadata);
				},

				canUpdate: function(identifier, schema, metadata) {
					return canDo(serviceUrls.draftSecurityUrl(), "update", identifier, schema, metadata);
				},

				canDelete: function(identifier, schema, metadata) {
					return canDo(serviceUrls.draftSecurityUrl(), "delete", identifier, schema, metadata);
				}
			},

			"locks" : {
				//===========================
				//
				//===========================
				"get" : function(identifier, params)
				{
					params            = clone(params||{});
					params.identifier = identifier;

					var useCache = !!params.cache;

					if(!params.cache)
						params.cache = true;

					var oTrans = transformPath(serviceUrls.draftLockUrl(), params);

					return $http.get(oTrans.url, { params : oTrans.params, cache:useCache });

					//TODO: return result.data;

				},
				//===========================
				//
				//===========================
				"exists" : function(identifier, params)
				{
					params            = clone(params||{});
					params.identifier = identifier;

					var useCache = !!params.cache;

					if(!params.cache)
						params.cache = true;

					var oTrans = transformPath(serviceUrls.draftLockUrl(), params);

					return $http.head(oTrans.url, {  params : oTrans.params, cache:useCache }).then(function() {

						return true;

					}).catch(function(error) {

						if(error.status!="404")
							throw "Error";

						return false;
					});
				},

				//===========================
				//
				//===========================
				"put" : function(identifier, params)
				{
					params            = clone(params||{});
					params.identifier = identifier;

					var oTrans = transformPath(serviceUrls.draftLockUrl(), params);
					var data = null;
					return $http.put(oTrans.url,data, { "params" : oTrans.params }).then(function(result){
						return result.data;
					});
				},
				//===========================
				//
				// Not tested
				//
				//===========================
				"delete" : function(identifier, lockID)
				{
					var params = {
						identifier : identifier,
						lockID     : lockID
					};

					var oTrans = transformPath(serviceUrls.draftLockUrl(), params);

					return $http.delete(oTrans.url).then(function(success) {
						return success.data;
					});
				}
			}
		};

		this.attachments = {

			//===========================
			//
			// Not tested
			//
			//===========================
			"put" : function(identifier, file, params)
			{
				params            = params || {};
				params.identifier = identifier;
				params.filename   = file.name;

				var contentType = params.contentType || getMimeTypes(file.name, file.type || "application/octet-stream");

				params.contentType = undefined;

				var oTrans = transformPath(serviceUrls.attachmentUrl(), params);

				return $http.put(oTrans.url, file, {
					"headers" : { "Content-Type": contentType },
					"params"  : oTrans.params
				}).then(
				function(success) {
					return angular.extend(success.data || {}, { "url": oTrans.url });
				},
				function(error) {
					error.data = angular.extend(error.data || {}, { "url": oTrans.url });
					return error;
				});
			},

			"getMimeType": function(file)
			{
				return getMimeTypes(file.name, file.type || "application/octet-stream");
			}
		};

		//==================================================
		//
		// Document Query
		//
		//==================================================
		this.documentQuery = {

			//===========================
			//
			//===========================
			"facets" : function(filter, params)
			{
			params            = _.extend({}, params||{});
			params.$filter    = filter;

			var useCache = !!params.cache;
			if(!params.cache)
				params.cache = true;

			var oTrans = transformPath(serviceUrls.documentFacetsQueryUrl(), params);

			return $http.get(oTrans.url, {  params : oTrans.params, cache:useCache });

			//TODO: return result.data;
			}

		};

		//==================================================
		//
		//
		//==================================================
		var getMimeTypes = function(filename, defaultMimeType) {
			var sMimeType = defaultMimeType || "application/octet-stream";

			if (filename && sMimeType == "application/octet-stream")
			{
				var sExt   = "";
				var iIndex = filename.lastIndexOf(".");

				if (iIndex >= 0)
					sExt = filename.substring(iIndex).toLowerCase();

				if (sExt == ".json")    sMimeType = "application/json";
				if (sExt == ".geojson") sMimeType = "application/json";
				if (sExt == ".xml")     sMimeType = "application/xml";
			}

			return sMimeType;
		};

		//==================================================
		//
		//
		//==================================================
		var clone = function(obj) {

			if (obj === null)
				return null;

			if (obj === undefined)
				return undefined;

			return angular.fromJson(angular.toJson(obj));
		};

		//===========================
		//
		// Replace :xyz in path with value in params
		// query part will be done by $http
		//
		//===========================
		var transformPath = function(url, params)
		{
			var oRegex     = /\:\w+/g;
			var oMatch     = null;
			var qMatches   = [];
			var oNewParams = {};

			while ((oMatch = oRegex.exec(url)) != null) {
				oMatch.key   = oMatch[0].substring(1);
				oMatch.value = oMatch[0];
				qMatches.splice(0, 0, oMatch);
			}

			for(var key in params||{}) {
				var bExist = false;

				for(var k in qMatches) {
					if (qMatches[k].key != key)
						continue;

					bExist = true;
					qMatches[k].value = params[key].toString();
				}

				if(!bExist)
					oNewParams[key] = params[key];
			}

			for (var j in qMatches) {
				url = replaceAt(url, qMatches[j].index, qMatches[j][0].length, encodeURIComponent(qMatches[j].value));
			}

			return { "url" : url, "params" : oNewParams };
		};

		//===========================
		//
		// Calls storage security
		//
		//===========================
		var canDo = function(patternPath, operation, identifier, schema, metadata) {

			metadata = angular.extend({}, { 'schema': schema }, metadata || {});

			if (!metadata.government && authentication.user().government)
				metadata = angular.extend(metadata, { 'government': authentication.user().government });

			if (!metadata.realm && defaultRealm)
				metadata = angular.extend(metadata, { 'realm': defaultRealm });

			var params = {
				"identifier" : identifier,
				"operation"  : operation,
				"metadata"   : metadata
			};

			var oTrans = transformPath(patternPath, params);

			return $http.get(oTrans.url, { "params": oTrans.params }).then(
				function(res) {
					return res.data.isAllowed;
				});
		};

		//===========================
		//
		// Replace :xyz in path with value in params
		// query part will be done by $http
		//
		//===========================
		var replaceAt = function(str, index, len, newText) {
			return str.substring(0, index) + newText + str.substring(index+len);
		};

		return this;
	}

    return new IStorage();
}]);
});
<|endoftext|>


<commit_before>
const iframeWhitelist = [
    {
        re: /^(https?:)?\/\/player.vimeo.com\/video\/.*/i,
        fn: (src) => {
            //
            if(!src) return null
            const m = src.match(/https:\/\/player\.vimeo\.com\/video\/([0-9]+)/)
            if(!m || m.length !== 2) return null
            return 'https://player.vimeo.com/video/' + m[1]
        }
    },
    { re: /^(https?:)?\/\/www.youtube.com\/embed\/.*/i,
      fn: (src) => {
        return src.replace(/\?.+$/, ''); // strip query string (yt: autoplay=1,controls=0,showinfo=0, etc)
      }
    },
    {
        re: /^https:\/\/w.soundcloud.com\/player\/.*/i,
        fn: (src) => {
            if(!src) return null
            //
            const m = src.match(/url=(.+?)&/)
            if(!m || m.length !== 2) return null
            return 'https://w.soundcloud.com/player/?url=' + m[1] +
                '&auto_play=false&hide_related=false&show_comments=true' +
                '&show_user=true&show_reposts=false&visual=true'
        }
    }
];
export const noImageText = '(Image not shown due to low ratings)'
export const allowedTags = `
    div, iframe, del,
    a, p, b, q, br, ul, li, ol, img, h1, h2, h3, h4, h5, h6, hr,
    blockquote, pre, code, em, strong, center, table, thead, tbody, tr, th, td,
    strike, sup, sub
`.trim().split(/,\s*/)

// Medium insert plugin uses: div, figure, figcaption, iframe
export default ({large = true, highQualityPost = true, noImage = false, sanitizeErrors = []}) => ({
    allowedTags,
        // figure, figcaption,

    // SEE https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
    allowedAttributes: {
        // "src" MUST pass a whitelist (below)
        iframe: ['src', 'width', 'height', 'frameborder', 'allowfullscreen',
            'webkitallowfullscreen', 'mozallowfullscreen'],

        // class attribute is strictly whitelisted (below)
        div: ['class'],

        // style is subject to attack, filtering more below
        td: ['style'],
        img: ['src', 'alt'],
        a: ['href', 'rel'],
    },
    transformTags: {
        iframe: (tagName, attribs) => {
            const srcAtty = attribs.src;
            for(const item of iframeWhitelist)
                if(item.re.test(srcAtty)) {
                    const src = typeof item.fn === 'function' ? item.fn(srcAtty, item.re) : srcAtty
                    if(!src) break
                    return {
                        tagName: 'iframe',
                        attribs: {
                            frameborder: '0',
                            allowfullscreen: 'allowfullscreen',
                            webkitallowfullscreen: 'webkitallowfullscreen', // deprecated but required for vimeo : https://vimeo.com/forums/help/topic:278181
                            mozallowfullscreen: 'mozallowfullscreen',       // deprecated but required for vimeo
                            src,
                            width:  large ? '640' : '480',
                            height: large ? '360' : '270',
                        },
                    }
                }
            console.log('Blocked, did not match iframe "src" white list urls:', tagName, attribs)
            sanitizeErrors.push('Invalid iframe URL: ' + srcAtty)
            return {tagName: 'div', text: `(Unsupported ${srcAtty})`}
        },
        img: (tagName, attribs) => {
            if(noImage) return {tagName: 'div', text: noImageText}
            //See https://github.com/punkave/sanitize-html/issues/117
            let {src, alt} = attribs
            if(!/^(https?:)?\/\//i.test(src)) {
                console.log('Blocked, image tag src does not appear to be a url', tagName, attribs)
                sanitizeErrors.push('An image in this post did not save properly.')
                return {tagName: 'img', attribs: {src: 'brokenimg.jpg'}}
            }

            // replace http:// with // to force https when needed
            src = src.replace(/^http:\/\//i, '//')

            let atts = {src}
            if(alt && alt !== '') atts.alt = alt
            return {tagName, attribs: atts}
        },
        div: (tagName, attribs) => {
            const attys = {}
            const classWhitelist = ['pull-right', 'pull-left', 'text-justify', 'text-rtl', 'text-center', 'text-right', 'videoWrapper']
            const validClass = classWhitelist.find(e => attribs.class == e)
            if(validClass)
                attys.class = validClass
            return {
                tagName,
                attribs: attys
            }
        },
        td: (tagName, attribs) => {
            const attys = {}
            if(attribs.style === 'text-align:right')
                attys.style = 'text-align:right'
            return {
                tagName,
                attribs: attys
            }
        },
        a: (tagName, attribs) => {
            let {href} = attribs
            if(!href) href = '#'
            href = href.trim()
            const attys = {href}
            // If it's not a (relative or absolute) steemit URL...
            if (!href.match(/^(\/(?!\/)|https:\/\/steemit.com)/)) {
                // attys.target = '_blank' // pending iframe impl https://mathiasbynens.github.io/rel-noopener/
                attys.rel = highQualityPost ? 'noopener' : 'nofollow noopener'
            }
            return {
                tagName,
                attribs: attys
            }
        },
    }
})
<commit_msg>b and strong accepted both; em accepted but not i; accept i too (#1443)<commit_after>
const iframeWhitelist = [
    {
        re: /^(https?:)?\/\/player.vimeo.com\/video\/.*/i,
        fn: (src) => {
            //
            if(!src) return null
            const m = src.match(/https:\/\/player\.vimeo\.com\/video\/([0-9]+)/)
            if(!m || m.length !== 2) return null
            return 'https://player.vimeo.com/video/' + m[1]
        }
    },
    { re: /^(https?:)?\/\/www.youtube.com\/embed\/.*/i,
      fn: (src) => {
        return src.replace(/\?.+$/, ''); // strip query string (yt: autoplay=1,controls=0,showinfo=0, etc)
      }
    },
    {
        re: /^https:\/\/w.soundcloud.com\/player\/.*/i,
        fn: (src) => {
            if(!src) return null
            //
            const m = src.match(/url=(.+?)&/)
            if(!m || m.length !== 2) return null
            return 'https://w.soundcloud.com/player/?url=' + m[1] +
                '&auto_play=false&hide_related=false&show_comments=true' +
                '&show_user=true&show_reposts=false&visual=true'
        }
    }
];
export const noImageText = '(Image not shown due to low ratings)'
export const allowedTags = `
    div, iframe, del,
    a, p, b, i, q, br, ul, li, ol, img, h1, h2, h3, h4, h5, h6, hr,
    blockquote, pre, code, em, strong, center, table, thead, tbody, tr, th, td,
    strike, sup, sub
`.trim().split(/,\s*/)

// Medium insert plugin uses: div, figure, figcaption, iframe
export default ({large = true, highQualityPost = true, noImage = false, sanitizeErrors = []}) => ({
    allowedTags,
        // figure, figcaption,

    // SEE https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
    allowedAttributes: {
        // "src" MUST pass a whitelist (below)
        iframe: ['src', 'width', 'height', 'frameborder', 'allowfullscreen',
            'webkitallowfullscreen', 'mozallowfullscreen'],

        // class attribute is strictly whitelisted (below)
        div: ['class'],

        // style is subject to attack, filtering more below
        td: ['style'],
        img: ['src', 'alt'],
        a: ['href', 'rel'],
    },
    transformTags: {
        iframe: (tagName, attribs) => {
            const srcAtty = attribs.src;
            for(const item of iframeWhitelist)
                if(item.re.test(srcAtty)) {
                    const src = typeof item.fn === 'function' ? item.fn(srcAtty, item.re) : srcAtty
                    if(!src) break
                    return {
                        tagName: 'iframe',
                        attribs: {
                            frameborder: '0',
                            allowfullscreen: 'allowfullscreen',
                            webkitallowfullscreen: 'webkitallowfullscreen', // deprecated but required for vimeo : https://vimeo.com/forums/help/topic:278181
                            mozallowfullscreen: 'mozallowfullscreen',       // deprecated but required for vimeo
                            src,
                            width:  large ? '640' : '480',
                            height: large ? '360' : '270',
                        },
                    }
                }
            console.log('Blocked, did not match iframe "src" white list urls:', tagName, attribs)
            sanitizeErrors.push('Invalid iframe URL: ' + srcAtty)
            return {tagName: 'div', text: `(Unsupported ${srcAtty})`}
        },
        img: (tagName, attribs) => {
            if(noImage) return {tagName: 'div', text: noImageText}
            //See https://github.com/punkave/sanitize-html/issues/117
            let {src, alt} = attribs
            if(!/^(https?:)?\/\//i.test(src)) {
                console.log('Blocked, image tag src does not appear to be a url', tagName, attribs)
                sanitizeErrors.push('An image in this post did not save properly.')
                return {tagName: 'img', attribs: {src: 'brokenimg.jpg'}}
            }

            // replace http:// with // to force https when needed
            src = src.replace(/^http:\/\//i, '//')

            let atts = {src}
            if(alt && alt !== '') atts.alt = alt
            return {tagName, attribs: atts}
        },
        div: (tagName, attribs) => {
            const attys = {}
            const classWhitelist = ['pull-right', 'pull-left', 'text-justify', 'text-rtl', 'text-center', 'text-right', 'videoWrapper']
            const validClass = classWhitelist.find(e => attribs.class == e)
            if(validClass)
                attys.class = validClass
            return {
                tagName,
                attribs: attys
            }
        },
        td: (tagName, attribs) => {
            const attys = {}
            if(attribs.style === 'text-align:right')
                attys.style = 'text-align:right'
            return {
                tagName,
                attribs: attys
            }
        },
        a: (tagName, attribs) => {
            let {href} = attribs
            if(!href) href = '#'
            href = href.trim()
            const attys = {href}
            // If it's not a (relative or absolute) steemit URL...
            if (!href.match(/^(\/(?!\/)|https:\/\/steemit.com)/)) {
                // attys.target = '_blank' // pending iframe impl https://mathiasbynens.github.io/rel-noopener/
                attys.rel = highQualityPost ? 'noopener' : 'nofollow noopener'
            }
            return {
                tagName,
                attribs: attys
            }
        },
    }
})
<|endoftext|>


<commit_before>(function(phina, PIXI) {
  phina.define('phina.pixi.PixiSprite', {
    superClass: phina.pixi.PixiElement,

    init: function(image, x, y, width, height) {
      this.superInit({
        pixiObject: new PIXI.Sprite(),
      });

      if (image) {
        this.setImage(image, x, y, width, height);
      }
    },

    setImage: function(image, x, y, width, height) {
      if (typeof image === 'string') {
        image = phina.asset.AssetManager.get('pixi', image);
      }
      if (typeof x === 'number') {
        image = image.createFrame(x, y, width, height);
      }
      this._image = image;

      this.pixiObject.texture = image.pixiTexture;
      this.pixiObject.anchor.set(0.5, 0.5);
      this.width = image.pixiTexture.width;
      this.height = image.pixiTexture.height;
      return this;
    },

    setFrame: function(x, y, width, height) {
      this._image = this._image.createFrame(x, y, width, height);
      return this;
    },

    _accessor: {
      image: {
        get: function() { return this._image; },
        set: function(v) {
          this.setImage(v);
        }
      },
    }
  });

}(phina, PIXI));<commit_msg>add sprite from shape and canvas<commit_after>(function(phina, PIXI) {
  phina.define('phina.pixi.PixiSprite', {
    superClass: phina.pixi.PixiElement,

    init: function(image, x, y, width, height) {
      this.superInit({
        pixiObject: new PIXI.Sprite(),
      });

      if (image) {
        this.setImage(image, x, y, width, height);
      }
    },

    setImage: function(image, x, y, width, height) {
      if (typeof image === 'string') {
        image = phina.asset.AssetManager.get('pixi', image);
      }
      if (typeof x === 'number') {
        image = image.createFrame(x, y, width, height);
      }
      this._image = image;

      this.pixiObject.texture = image.pixiTexture;
      this.pixiObject.anchor.set(0.5, 0.5);
      this.width = image.pixiTexture.width;
      this.height = image.pixiTexture.height;
      return this;
    },

    setFrame: function(x, y, width, height) {
      this._image = this._image.createFrame(x, y, width, height);
      return this;
    },
    /**
     * @param {HTMLCanvasElement} canvas
     */
    fromCanvas: function(canvas, x, y, width, height) {
      return this.setImage(phina.pixi.PixiTexture().fromCanvas(canvas), x, y, width, height);
    },
    /**
     * @param {phina.display.Shape} shape
     */
    fromShape: function(shape, x, y, width, height) {
      return this.setImage(phina.pixi.PixiTexture().fromShape(shape), x, y, width, height);
    },

    _accessor: {
      image: {
        get: function() { return this._image; },
        set: function(v) {
          this.setImage(v);
        }
      },
    },

    _static: {
      fromCanvas: function(canvas, x, y, width, height) {
        return phina.pixi.PixiSprite().fromCanvas(canvas, x, y, width, height);
      },

      fromShape: function(shape, x, y, width, height) {
        return phina.pixi.PixiSprite().fromShape(shape, x, y, width, height);
      },
    }
  });

}(phina, PIXI));<|endoftext|>


<commit_before>import React from 'react'

import Header from '../components/Header';
import Body from '../components/Body';
import Footer from '../components/Footer';
import CollectivesWithData from '../components/CollectivesWithData';
import withLoggedInUser from '../lib/withLoggedInUser';
import withIntl from '../lib/withIntl';
import withData from '../lib/withData'
import colors from '../constants/colors';

import Container from '../components/Container';
import Button from '../components/Button';
import { P, H1, H5 } from '../components/Text';
import { Flex, Box } from 'grid-styled';
import { FormattedMessage, defineMessages } from 'react-intl';

import GiftCard from '../components/GiftCard';
import SearchForm from '../components/SearchForm';
import styled from 'styled-components';
import { backgroundSize, fontSize, minHeight, maxWidth } from 'styled-system';

const Title = styled(H1)`
  color: white;
  ${fontSize};
`;

const Subtitle = styled(H5)`
  color: white;
  ${fontSize};
  ${maxWidth};
`;

const SearchFormContainer = styled(Box)`
  margin: 64px auto;
  text-align: center;
`;

const Hero = styled(Box)`
  width: 100%;
  ${minHeight};
  background-image: url('/static/images/redeem-hero.svg');
  background-position: center top;
  background-repeat: no-repeat;
  background-size: auto 376px;
  ${backgroundSize}
`;

class RedeemedPage extends React.Component {

  static getInitialProps ({ query: { shortUUID } }) {
    return { shortUUID };
  }

  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.state = {
      loading: false,
      view: 'form',
      form: {},
      LoggedInUser: null,
    };

    this.messages = defineMessages({
      'email': { id: 'user.email.label', defaultMessage: 'email' },
      'code': { id: 'redeem.form.code.label', defaultMessage: 'Gift card code' },
    });

  }

  async componentDidMount() {
    const { getLoggedInUser } = this.props;
    const LoggedInUser = await getLoggedInUser();
    this.setState({ LoggedInUser });
  }

  async redeem() {
    this.setState({ loading: true });
    if (true) {
      this.setState({ loading: false, view: 'confirmEmail' });
    }
  }

  handleSubmit() {
    this.redeem();
  }

  handleChange(fieldname, value) {
    const { form } = this.state;
    form[fieldname] = value;
    this.setState({ form });
  }

  render() {
    const { LoggedInUser } = this.state;
    const { code } =  this.props;
    const emitter = { slug: 'triplebyte', name: 'Tripebyte' };
    return (

          <Flex alignItems="center" flexDirection="column">

            <Hero minHeight={['300px', null, '380px']} backgroundSize={['auto 300px', 'auto 380px']}>
              <Flex alignItems="center" flexDirection="column">

                <Box mt={5}>
                  <Title fontSize={['3rem', null, '4rem']}>Gift Card Redeemed! 🎉</Title>
                </Box>

                <Box mt={2}>
                  <Subtitle fontSize={['1.5rem', null, '2rem']} maxWidth={['90%', '640px']}>
                    <Box><FormattedMessage id="redeemed.subtitle.line1" defaultMessage="The card has been added to your account." /></Box>
                    <Box><FormattedMessage id="redeemed.subtitle.line2" defaultMessage="You can now donate to any collective of your choice." /></Box>
                  </Subtitle>
                </Box>

                <Box mt={[4,5]}>
                  <GiftCard
                    amount={10000}
                    currency="USD"
                    emitter={emitter}
                    name="Maria"
                    />
                </Box>
              </Flex>
            </Hero>

            <Box width={['320px', '640px']}>
              <SearchFormContainer>
                <Box mb={3}>

<FormattedMessage id="redeemed.findCollectives" defaultMessage="Find open collectives to support." />

                </Box>
                <SearchForm fontSize="1.4rem" />
              </SearchFormContainer>
            </Box>

              <FormattedMessage id="redeemed.suggestions" defaultMessage="or you can choose from these awesome collectives that are doing great work:" />

            <Box mb={5}>
              <Container maxWidth="1200px">
                <CollectivesWithData
                  HostCollectiveId={11004} // hard-coded to only show open source projects
                  orderBy="balance"
                  orderDirection="DESC"
                  limit={12}
                  />
              </Container>
            </Box>
          </Flex>

    );
  }
}

export default withData(withLoggedInUser(withIntl(RedeemedPage)));
<commit_msg>cleaned up /redeemed page and using query params to customize it<commit_after>import React from 'react';
import PropTypes from 'prop-types';

import Header from '../components/Header';
import Body from '../components/Body';
import Footer from '../components/Footer';
import CollectivesWithData from '../components/CollectivesWithData';
import withLoggedInUser from '../lib/withLoggedInUser';
import withIntl from '../lib/withIntl';
import withData from '../lib/withData';
import sanitizeHtml from 'sanitize-html';

import Container from '../components/Container';
import { P, H1, H5 } from '../components/Text';
import { Flex, Box } from 'grid-styled';
import { FormattedMessage } from 'react-intl';

import GiftCard from '../components/GiftCard';
import SearchForm from '../components/SearchForm';
import styled from 'styled-components';
import { backgroundSize, fontSize, minHeight, maxWidth } from 'styled-system';

const Title = styled(H1)`
  color: white;
  ${fontSize};
`;

const Subtitle = styled(H5)`
  color: white;
  ${fontSize};
  ${maxWidth};
`;

const SearchFormContainer = styled(Box)`
  margin: 64px auto;
  text-align: center;
`;

const Hero = styled(Box)`
  width: 100%;
  ${minHeight};
  background-image: url('/static/images/redeem-hero.svg');
  background-position: center top;
  background-repeat: no-repeat;
  background-size: auto 376px;
  ${backgroundSize}
`;

class RedeemedPage extends React.Component {

  static propTypes = {
    name: PropTypes.string,
    emitterSlug: PropTypes.string,
    emitterName: PropTypes.string,
    amount: PropTypes.number.isRequired,
  };

  static getInitialProps ({ query: { amount, name, emitterSlug, emitterName } }) {
    return {
      amount: amount && Number(amount),
      name: sanitizeHtml(name, { allowedTags: [], allowedAttributes: [] }),
      emitterSlug: sanitizeHtml(emitterSlug, { allowedTags: [], allowedAttributes: [] }),
      emitterName: sanitizeHtml(emitterName, { allowedTags: [], allowedAttributes: [] }),
    };
  }

  constructor(props) {
    super(props);
    this.state = {};
  }

  async componentDidMount() {
    const { getLoggedInUser } = this.props;
    const LoggedInUser = await getLoggedInUser();
    this.setState({ LoggedInUser });
  }

  render() {
    const { amount, emitterSlug, emitterName, name } =  this.props;
    return (

          <Flex alignItems="center" flexDirection="column">

            <Hero minHeight={['300px', null, '380px']} backgroundSize={['auto 300px', 'auto 380px']}>
              <Flex alignItems="center" flexDirection="column">

                <Box mt={5}>
                  <Title fontSize={['3rem', null, '4rem']}>Gift Card Redeemed! 🎉</Title>
                </Box>

                <Box mt={2}>
                  <Subtitle fontSize={['1.5rem', null, '2rem']} maxWidth={['90%', '640px']}>
                    <Box><FormattedMessage id="redeemed.subtitle.line1" defaultMessage="The card has been added to your account." /></Box>
                    <Box><FormattedMessage id="redeemed.subtitle.line2" defaultMessage="You can now donate to any collective of your choice." /></Box>
                  </Subtitle>
                </Box>

                <Box mt={[4,5]}>
                  <GiftCard
                    amount={amount}
                    currency="USD"
                    emitter={{ slug: emitterSlug, name: emitterName }}
                    name={name}
                    />
                </Box>
              </Flex>
            </Hero>

            <Box width={['320px', '640px']}>
              <SearchFormContainer>
                <Box mb={3}>

<FormattedMessage id="redeemed.findCollectives" defaultMessage="Find open collectives to support." />

                </Box>
                <SearchForm fontSize="1.4rem" />
              </SearchFormContainer>
            </Box>

              <FormattedMessage id="redeemed.suggestions" defaultMessage="or you can choose from these awesome collectives that are doing great work:" />

            <Box mb={5}>
              <Container maxWidth="1200px">
                <CollectivesWithData
                  HostCollectiveId={11004} // hard-coded to only show open source projects
                  orderBy="balance"
                  orderDirection="DESC"
                  limit={12}
                  />
              </Container>
            </Box>
          </Flex>

    );
  }
}

export default withData(withLoggedInUser(withIntl(RedeemedPage)));
<|endoftext|>


<commit_before>/**
 * Copyright 2015 CANAL+ Group
 *
 * 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 { Observable } from "rxjs/Observable";
import { EmptyObservable } from "rxjs/observable/EmptyObservable";
import { merge } from "rxjs/observable/merge";
import assert from "../../utils/assert";
import { resolveURL } from "../../utils/url";
import { parseSidx, patchPssh, getMdat } from "./mp4";
import { bytesToStr } from "../../utils/bytes.js";

// TODO Those should already be constructed here
import { Adaptation } from "../../manifest/adaptation.js";
import { Representation } from "../../manifest/representation.js";
import { Segment } from "../../manifest/segment.js";

import request from "../../request";
import dashManifestParser from "./manifest";

import { parseTTML } from "../parsers/texttracks/ttml.js";

const empty = EmptyObservable.create;

/**
 * Pad with 0 in the left of the given n argument to reach l length
 * @param {Number|string} n
 * @param {Number} l
 * @returns {string}
 */
function pad(n, l) {
  n = n.toString();
  if (n.length >= l) {
    return n;
  }
  const arr = new Array(l + 1).join("0") + n;
  return arr.slice(-l);
}

/**
 * Returns text-formatted byteRange (`bytes=$start-$end?)`
 * @param {Array.<string|Number>}
 * @returns {string}
 */
function byteRange([start, end]) {
  if (!end || end === Infinity) {
    return "bytes=" + (+start) + "-";
  } else {
    return "bytes=" + (+start) + "-" + (+end);
  }
}

/**
 * Add formatting when asked in a token (add padding to numbers).
 * @param {string|Number} replacer - the token value
 * @returns {Function} - @see replaceTokens
 */
function processFormatedToken(replacer) {
  return (match, format, widthStr) => {
    const width = widthStr ? parseInt(widthStr, 10) : 1;
    return pad(""+replacer, width);
  };
}

/**
 * Replace "tokens" written in a given path (e.g. $Time$) by the corresponding
 * infos, taken from the given segment.
 * @param {string} path
 * @param {Segment} segment
 * @returns {string}
 */
function replaceTokens(path, segment) {
  if (path.indexOf("$") === -1) {
    return path;
  } else {
    const rep = segment.getRepresentation();
    return path
      .replace(/\$\$/g, "$")
      .replace(/\$RepresentationID\$/g, rep.id)
      .replace(/\$Bandwidth(|\%0(\d+)d)\$/g, processFormatedToken(rep.bitrate))
      .replace(/\$Number(|\%0(\d+)d)\$/g, processFormatedToken(segment.getNumber()))
      .replace(/\$Time(|\%0(\d+)d)\$/g, processFormatedToken(segment.getTime()));
  }
}

/**
 * Returns true if the given texttrack segment represents a textrack embedded
 * in a mp4 file.
 * @param {Segment} segment - __TextTrack__ segment
 * @returns {Boolean}
 */
function isMP4EmbeddedTrack(segment) {
  return segment.getRepresentation().mimeType === "application/mp4";
}

/**
 * Returns pipelines used for DASH streaming.
 * @param {Object} options
 * @param {Function} [options.createXHR] - Optional custom XMLHttpRequest
 * implementation. Used for each generated http request.
 * @param {Function} [options.contentProtectionParser] - Optional parser for the
 * manifest's content Protection.
 * @returns {Object}
 */
export default function(options={}) {
  let { contentProtectionParser } = options;

  if (!contentProtectionParser) {
    contentProtectionParser = () => {};
  }

  const createXHR = options.createXHR;

  const segmentLoader = ({
    url,
    segment,
  }) => {
    const { range, indexRange } = segment;

    // fire a single time contiguous init and index ranges.
    if (
      range && indexRange &&
      range[1] === indexRange[0] - 1
    ) {
      return request({
        url: url,
        responseType: "arraybuffer",
        headers: {
          Range: byteRange([range[0], indexRange[1]]),
        },
        createXHR,
      });
    }

    const mediaHeaders = range ?
      { "Range": byteRange(range) } : null;

    const mediaOrInitRequest = request({
      url: url,
      responseType: "arraybuffer",
      headers: mediaHeaders,
      createXHR,
    });

    // If init segment has indexRange metadata, we need to fetch
    // both the initialization data and the index metadata. We do
    // this in parallel and send the both blobs into the pipeline.
    if (indexRange) {
      const indexRequest = request({
        url: url,
        responseType: "arraybuffer",
        headers: { "Range": byteRange(indexRange) },
        createXHR,
      });
      return merge(mediaOrInitRequest, indexRequest);
    }
    else {
      return mediaOrInitRequest;
    }
  };

  const segmentPreLoader = ({ segment }) => {
    const media = segment.getMedia();
    const range = segment.getRange();
    const indexRange = segment.getIndexRange();

    // init segment without initialization media/range/indexRange:
    // we do nothing on the network
    if (segment.isInitSegment() && !(media || range || indexRange)) {
      return empty();
    }

    // TODO add get helper
    const customSegmentLoader = options.segmentLoader;

    const path = media ?
      replaceTokens(media, segment) : "";

    const url = resolveURL(segment.getResolvedURL(), path);

    const args = {
      adaptation: new Adaptation(segment.getAdaptation()),
      representation: new Representation(segment.getRepresentation()),
      segment: new Segment(segment),
      transport: "smooth",
      url,
    };

    if (!customSegmentLoader) {
      return segmentLoader(args);
    }

    return Observable.create(obs => {
      let hasFinished = false;
      let hasFallbacked = false;

      const resolve = (args = {}) => {
        if (!hasFallbacked) {
          hasFinished = true;
          obs.next({
            responseData: args.data,
            size: args.size || 0,
            duration: args.duration || 0,
          });
          obs.complete();
        }
      };

      const reject = (err = {}) => {
        if (!hasFallbacked) {
          hasFinished = true;
          obs.error(err);
        }
      };

      const fallback = () => {
        hasFallbacked = true;
        segmentLoader(args).subscribe(obs);
      };

      const callbacks = { reject, resolve, fallback };
      const abort = customSegmentLoader(args, callbacks);

      return () => {
        if (!hasFinished && !hasFallbacked && typeof abort === "function") {
          abort();
        }
      };
    });
  };

  const manifestPipeline = {
    loader({ url }) {
      return request({
        url,
        responseType: "document",
        createXHR,
      });
    },

    parser({ response }) {
      const data = response.responseData;
      return Observable.of({
        manifest: dashManifestParser(data, contentProtectionParser),
        url: response.url,
      });
    },
  };

  const segmentPipeline = {
    loader({ segment }) {
      return segmentPreLoader({ segment });
    },

    parser({ segment, response }) {
      const responseData = new Uint8Array(response.responseData);

      // added segments and timescale informations are extracted from
      // sidx atom
      let nextSegments, timescale, currentSegment;

      // added index (segments and timescale) informations are
      // extracted from sidx atom
      const indexRange = segment.getIndexRange();
      const index = parseSidx(responseData, indexRange ? indexRange[0] : 0);
      if (index) {
        nextSegments = index.segments;
        timescale = index.timescale;
      }

      if (!segment.isInitSegment()) {
        // current segment information may originate from the index
        // itself in which case we don't have to use the index
        // segments.
        if (segment.getTime() >= 0 &&
            segment.getDuration() >= 0) {
          currentSegment = {
            ts: segment.getTime(),
            d: segment.getDuration(),
          };
        }
        else if (index && index.segments.length === 1) {
          currentSegment = {
            ts: index.segments[0].ts,
            d:  index.segments[0].d,
          };
        }

        if (__DEV__) {
          assert(currentSegment);
        }
      }

      let segmentData = responseData;
      if (segment.isInitSegment()) {
        const adaptation = segment.getAdaptation();
        if (adaptation.contentProtection) {
          segmentData = patchPssh(responseData, adaptation.contentProtection);
        }
      }

      return Observable.of({
        segmentData,
        currentSegment,
        nextSegments,
        timescale,
      });
    },
  };

  const textTrackPipeline = {
    // TODO DRY this (code too similar to segmentPipeline)
    loader({ segment }) {
      const media = segment.getMedia();
      const range = segment.getRange();
      const indexRange = segment.getIndexRange();

      const responseType = isMP4EmbeddedTrack(segment) >= 0 ?
        "arraybuffer" : "text";

      // init segment without initialization media/range/indexRange:
      // we do nothing on the network
      if (segment.isInitSegment() && !(media || range || indexRange)) {
        return empty();
      }

      const path = media ?
        replaceTokens(media, segment) : "";

      const mediaUrl = resolveURL(segment.getResolvedURL(), path);

      // fire a single time contiguous init and index ranges.
      if (
        range && indexRange &&
        range[1] === indexRange[0] - 1
      ) {
        return request({
          url: mediaUrl,
          responseType,
          headers: {
            Range: byteRange([range[0], indexRange[1]]),
          },
          createXHR,
        });
      }

      const mediaOrInitRequest = request({
        url: mediaUrl,
        responseType,
        headers: range ? {
          Range: byteRange(range),
        } : null,
        createXHR,
      });

      // If init segment has indexRange metadata, we need to fetch
      // both the initialization data and the index metadata. We do
      // this in parallel and send the both blobs into the pipeline.
      if (indexRange) {
        const indexRequest = request({
          url: mediaUrl,
          responseType,
          headers: {
            Range: byteRange(indexRange),
          },
          createXHR,
        });
        return merge(mediaOrInitRequest, indexRequest);
      }
      else {
        return mediaOrInitRequest;
      }
    },

    parser({ segment, response }) {
      const { lang } = segment.getAdaptation();

      let responseData;
      let text;

      if (isMP4EmbeddedTrack(segment)) {
        responseData = new Uint8Array(response.responseData);
        text = bytesToStr(getMdat(responseData));
      } else {
        responseData = response.responseData;
        text = responseData;
      }

      // added segments and timescale informations are extracted from
      // sidx atom
      let nextSegments, timescale, currentSegment;

      // added index (segments and timescale) informations are
      // extracted from sidx atom
      const indexRange = segment.getIndexRange();
      const index = parseSidx(responseData, indexRange ? indexRange[0] : 0);
      if (index) {
        nextSegments = index.segments;
        timescale = index.timescale;
      }

      let segmentData = [];

      if (!segment.isInitSegment()) {
        // current segment information may originate from the index
        // itself in which case we don't have to use the index
        // segments.
        if (segment.getTime() >= 0 &&
            segment.getDuration() >= 0) {
          currentSegment = {
            ts: segment.getTime(),
            d: segment.getDuration(),
          };
        }
        else if (index && index.segments.length === 1) {
          currentSegment = {
            ts: index.segments[0].ts,
            d:  index.segments[0].d,
          };
        }

        if (__DEV__) {
          assert(currentSegment);
        }

        // const timescale = (index && index.timescale) ||
        //   representationIndex.timescale;

        const { codecs = "" } = segment.getRepresentation();

        switch (codecs.toLowerCase()) {
        case "stpp":
          segmentData = parseTTML(text, lang, 0);
          break;
        default:
          console.warn("The codec used for the subtitle is not managed yet.");
        }
      }

      return Observable.of({
        segmentData,
        currentSegment,
        nextSegments,
        timescale,
      });
    },
  };

  return {
    directFile: false,
    manifest: manifestPipeline,
    audio: segmentPipeline,
    video: segmentPipeline,
    text: textTrackPipeline,
  };
};
<commit_msg>DASH: add BIF support<commit_after>/**
 * Copyright 2015 CANAL+ Group
 *
 * 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 { Observable } from "rxjs/Observable";
import { EmptyObservable } from "rxjs/observable/EmptyObservable";
import { merge } from "rxjs/observable/merge";
import assert from "../../utils/assert";
import { resolveURL } from "../../utils/url";
import { parseSidx, patchPssh, getMdat } from "./mp4";
import { bytesToStr } from "../../utils/bytes.js";

// TODO Those should already be constructed here
import { Adaptation } from "../../manifest/adaptation.js";
import { Representation } from "../../manifest/representation.js";
import { Segment } from "../../manifest/segment.js";

import request from "../../request";
import dashManifestParser from "./manifest";

import { parseTTML } from "../parsers/texttracks/ttml.js";
import { parseBif } from "../parsers/bif.js";

const empty = EmptyObservable.create;

/**
 * Pad with 0 in the left of the given n argument to reach l length
 * @param {Number|string} n
 * @param {Number} l
 * @returns {string}
 */
function pad(n, l) {
  n = n.toString();
  if (n.length >= l) {
    return n;
  }
  const arr = new Array(l + 1).join("0") + n;
  return arr.slice(-l);
}

/**
 * Returns text-formatted byteRange (`bytes=$start-$end?)`
 * @param {Array.<string|Number>}
 * @returns {string}
 */
function byteRange([start, end]) {
  if (!end || end === Infinity) {
    return "bytes=" + (+start) + "-";
  } else {
    return "bytes=" + (+start) + "-" + (+end);
  }
}

/**
 * Add formatting when asked in a token (add padding to numbers).
 * @param {string|Number} replacer - the token value
 * @returns {Function} - @see replaceTokens
 */
function processFormatedToken(replacer) {
  return (match, format, widthStr) => {
    const width = widthStr ? parseInt(widthStr, 10) : 1;
    return pad(""+replacer, width);
  };
}

/**
 * Replace "tokens" written in a given path (e.g. $Time$) by the corresponding
 * infos, taken from the given segment.
 * @param {string} path
 * @param {Segment} segment
 * @returns {string}
 */
function replaceTokens(path, segment) {
  if (path.indexOf("$") === -1) {
    return path;
  } else {
    const rep = segment.getRepresentation();
    return path
      .replace(/\$\$/g, "$")
      .replace(/\$RepresentationID\$/g, rep.id)
      .replace(/\$Bandwidth(|\%0(\d+)d)\$/g, processFormatedToken(rep.bitrate))
      .replace(/\$Number(|\%0(\d+)d)\$/g, processFormatedToken(segment.getNumber()))
      .replace(/\$Time(|\%0(\d+)d)\$/g, processFormatedToken(segment.getTime()));
  }
}

/**
 * Returns true if the given texttrack segment represents a textrack embedded
 * in a mp4 file.
 * @param {Segment} segment - __TextTrack__ segment
 * @returns {Boolean}
 */
function isMP4EmbeddedTrack(segment) {
  return segment.getRepresentation().mimeType === "application/mp4";
}

/**
 * Returns pipelines used for DASH streaming.
 * @param {Object} options
 * @param {Function} [options.createXHR] - Optional custom XMLHttpRequest
 * implementation. Used for each generated http request.
 * @param {Function} [options.contentProtectionParser] - Optional parser for the
 * manifest's content Protection.
 * @returns {Object}
 */
export default function(options={}) {
  let { contentProtectionParser } = options;

  if (!contentProtectionParser) {
    contentProtectionParser = () => {};
  }

  const createXHR = options.createXHR;

  const segmentLoader = ({
    url,
    segment,
  }) => {
    const { range, indexRange } = segment;

    // fire a single time contiguous init and index ranges.
    if (
      range && indexRange &&
      range[1] === indexRange[0] - 1
    ) {
      return request({
        url: url,
        responseType: "arraybuffer",
        headers: {
          Range: byteRange([range[0], indexRange[1]]),
        },
        createXHR,
      });
    }

    const mediaHeaders = range ?
      { "Range": byteRange(range) } : null;

    const mediaOrInitRequest = request({
      url: url,
      responseType: "arraybuffer",
      headers: mediaHeaders,
      createXHR,
    });

    // If init segment has indexRange metadata, we need to fetch
    // both the initialization data and the index metadata. We do
    // this in parallel and send the both blobs into the pipeline.
    if (indexRange) {
      const indexRequest = request({
        url: url,
        responseType: "arraybuffer",
        headers: { "Range": byteRange(indexRange) },
        createXHR,
      });
      return merge(mediaOrInitRequest, indexRequest);
    }
    else {
      return mediaOrInitRequest;
    }
  };

  const segmentPreLoader = ({ segment }) => {
    const media = segment.getMedia();
    const range = segment.getRange();
    const indexRange = segment.getIndexRange();

    // init segment without initialization media/range/indexRange:
    // we do nothing on the network
    if (segment.isInitSegment() && !(media || range || indexRange)) {
      return empty();
    }

    // TODO add get helper
    const customSegmentLoader = options.segmentLoader;

    const path = media ?
      replaceTokens(media, segment) : "";

    const url = resolveURL(segment.getResolvedURL(), path);

    const args = {
      adaptation: new Adaptation(segment.getAdaptation()),
      representation: new Representation(segment.getRepresentation()),
      segment: new Segment(segment),
      transport: "smooth",
      url,
    };

    if (!customSegmentLoader) {
      return segmentLoader(args);
    }

    return Observable.create(obs => {
      let hasFinished = false;
      let hasFallbacked = false;

      const resolve = (args = {}) => {
        if (!hasFallbacked) {
          hasFinished = true;
          obs.next({
            responseData: args.data,
            size: args.size || 0,
            duration: args.duration || 0,
          });
          obs.complete();
        }
      };

      const reject = (err = {}) => {
        if (!hasFallbacked) {
          hasFinished = true;
          obs.error(err);
        }
      };

      const fallback = () => {
        hasFallbacked = true;
        segmentLoader(args).subscribe(obs);
      };

      const callbacks = { reject, resolve, fallback };
      const abort = customSegmentLoader(args, callbacks);

      return () => {
        if (!hasFinished && !hasFallbacked && typeof abort === "function") {
          abort();
        }
      };
    });
  };

  const manifestPipeline = {
    loader({ url }) {
      return request({
        url,
        responseType: "document",
        createXHR,
      });
    },

    parser({ response }) {
      const data = response.responseData;
      return Observable.of({
        manifest: dashManifestParser(data, contentProtectionParser),
        url: response.url,
      });
    },
  };

  const segmentPipeline = {
    loader({ segment }) {
      return segmentPreLoader({ segment });
    },

    parser({ segment, response }) {
      const responseData = new Uint8Array(response.responseData);

      // added segments and timescale informations are extracted from
      // sidx atom
      let nextSegments, timescale, currentSegment;

      // added index (segments and timescale) informations are
      // extracted from sidx atom
      const indexRange = segment.getIndexRange();
      const index = parseSidx(responseData, indexRange ? indexRange[0] : 0);
      if (index) {
        nextSegments = index.segments;
        timescale = index.timescale;
      }

      if (!segment.isInitSegment()) {
        // current segment information may originate from the index
        // itself in which case we don't have to use the index
        // segments.
        if (segment.getTime() >= 0 &&
            segment.getDuration() >= 0) {
          currentSegment = {
            ts: segment.getTime(),
            d: segment.getDuration(),
          };
        }
        else if (index && index.segments.length === 1) {
          currentSegment = {
            ts: index.segments[0].ts,
            d:  index.segments[0].d,
          };
        }

        if (__DEV__) {
          assert(currentSegment);
        }
      }

      let segmentData = responseData;
      if (segment.isInitSegment()) {
        const adaptation = segment.getAdaptation();
        if (adaptation.contentProtection) {
          segmentData = patchPssh(responseData, adaptation.contentProtection);
        }
      }

      return Observable.of({
        segmentData,
        currentSegment,
        nextSegments,
        timescale,
      });
    },
  };

  const textTrackPipeline = {
    // TODO DRY this (code too similar to segmentPipeline)
    loader({ segment }) {
      const media = segment.getMedia();
      const range = segment.getRange();
      const indexRange = segment.getIndexRange();

      const responseType = isMP4EmbeddedTrack(segment) >= 0 ?
        "arraybuffer" : "text";

      // init segment without initialization media/range/indexRange:
      // we do nothing on the network
      if (segment.isInitSegment() && !(media || range || indexRange)) {
        return empty();
      }

      const path = media ?
        replaceTokens(media, segment) : "";

      const mediaUrl = resolveURL(segment.getResolvedURL(), path);

      // fire a single time contiguous init and index ranges.
      if (
        range && indexRange &&
        range[1] === indexRange[0] - 1
      ) {
        return request({
          url: mediaUrl,
          responseType,
          headers: {
            Range: byteRange([range[0], indexRange[1]]),
          },
          createXHR,
        });
      }

      const mediaOrInitRequest = request({
        url: mediaUrl,
        responseType,
        headers: range ? {
          Range: byteRange(range),
        } : null,
        createXHR,
      });

      // If init segment has indexRange metadata, we need to fetch
      // both the initialization data and the index metadata. We do
      // this in parallel and send the both blobs into the pipeline.
      if (indexRange) {
        const indexRequest = request({
          url: mediaUrl,
          responseType,
          headers: {
            Range: byteRange(indexRange),
          },
          createXHR,
        });
        return merge(mediaOrInitRequest, indexRequest);
      }
      else {
        return mediaOrInitRequest;
      }
    },

    parser({ segment, response }) {
      const { lang } = segment.getAdaptation();

      let responseData;
      let text;

      if (isMP4EmbeddedTrack(segment)) {
        responseData = new Uint8Array(response.responseData);
        text = bytesToStr(getMdat(responseData));
      } else {
        responseData = response.responseData;
        text = responseData;
      }

      // added segments and timescale informations are extracted from
      // sidx atom
      let nextSegments, timescale, currentSegment;

      // added index (segments and timescale) informations are
      // extracted from sidx atom
      const indexRange = segment.getIndexRange();
      const index = parseSidx(responseData, indexRange ? indexRange[0] : 0);
      if (index) {
        nextSegments = index.segments;
        timescale = index.timescale;
      }

      let segmentData = [];

      if (!segment.isInitSegment()) {
        // current segment information may originate from the index
        // itself in which case we don't have to use the index
        // segments.
        if (segment.getTime() >= 0 &&
            segment.getDuration() >= 0) {
          currentSegment = {
            ts: segment.getTime(),
            d: segment.getDuration(),
          };
        }
        else if (index && index.segments.length === 1) {
          currentSegment = {
            ts: index.segments[0].ts,
            d:  index.segments[0].d,
          };
        }

        if (__DEV__) {
          assert(currentSegment);
        }

        // const timescale = (index && index.timescale) ||
        //   representationIndex.timescale;

        const { codecs = "" } = segment.getRepresentation();

        switch (codecs.toLowerCase()) {
        case "stpp":
          segmentData = parseTTML(text, lang, 0);
          break;
        default:
          console.warn("The codec used for the subtitle is not managed yet.");
        }
      }

      return Observable.of({
        segmentData,
        currentSegment,
        nextSegments,
        timescale,
      });
    },

  };

  const imageTrackPipeline = {
    loader({ segment }) {
      if (segment.init) {
        return empty();
      } else {
        const media = segment.getMedia();
        const path = media ?
          replaceTokens(media, segment) : "";
        const mediaUrl = resolveURL(segment.getResolvedURL(), path);
        return request({
          url: mediaUrl,
          responseType: "arraybuffer",
          createXHR,
        });
      }
    },

    parser({ response /*, adaptation, representation, segment */ }) {
      const responseData = response.responseData;
      const blob = new Uint8Array(responseData);

      const currentSegment = {
        ts: 0,
        d:  Infinity,
      };

      let segmentData, timescale;
      if (blob) {
        const bif = parseBif(blob);
        segmentData = bif.thumbs;
        timescale   = bif.timescale;

        // var firstThumb = blob[0];
        // var lastThumb  = blob[blob.length - 1];

        // currentSegment = {
        //   ts: firstThumb.ts,
        //   d:  lastThumb.ts
        // };
      }

      return Observable.of({
        segmentData,
        currentSegment,
        timescale,
      });
    },
  };

  return {
    directFile: false,
    manifest: manifestPipeline,
    audio: segmentPipeline,
    video: segmentPipeline,
    text: textTrackPipeline,
    image: imageTrackPipeline,
  };
}
<|endoftext|>


<commit_before>/**
 * Generate RSA key pair
 *
 * @param bitlen desired modulus length, default is 2048
 * @param e public exponent, default is 65537
 */
function RSA_generateKey ( bitlen, e ) {
    bitlen = bitlen || 2048;
    e      = e      || 65537;

    if ( bitlen < 512 )
        throw new IllegalArgumentError("bit length is too small");

    var m, e, d, p, q, p1, q1, dp, dq, u;

    p = BigNumber_randomProbablePrime(
        bitlen >> 1,
        function ( p ) {
            p1 = p.subtract(BigNumber_ONE);
            return BigNumber_extGCD( p1, e ).gcd.valueOf() == 1;
        }
    );

    q = BigNumber_randomProbablePrime(
        bitlen - (bitlen >> 1),
        function ( q ) {
            m = new Modulus( p.multiply(q) );
            if ( !( m.limbs[ ( (bitlen + 31) >> 5 ) - 1 ] >>> ( (bitlen - 1) & 31) ) ) return false;
            q1 = q.subtract(BigNumber_ONE);
            return BigNumber_extGCD( q1, e ).gcd.valueOf() == 1;
        }
    );

    d = new Modulus( p1.multiply(q1) ).inverse(e);

    dp = d.divide(p1).remainder,
    dq = d.divide(q1).remainder;

    p = new Modulus(p),
    q = new Modulus(q);

    var u = p.inverse(q);

    return [ m, e, d, p, q, dp, dq, u ];
}

RSA.generateKey = RSA_generateKey;
<commit_msg>RSA.generateKey minor improvement<commit_after>/**
 * Generate RSA key pair
 *
 * @param bitlen desired modulus length, default is 2048
 * @param e public exponent, default is 65537
 */
function RSA_generateKey ( bitlen, e ) {
    bitlen = bitlen || 2048;
    e      = e      || 65537;

    if ( bitlen < 512 )
        throw new IllegalArgumentError("bit length is too small");

    var m, e, d, p, q, p1, q1, dp, dq, u;

    p = BigNumber_randomProbablePrime(
        bitlen >> 1,
        function ( p ) {
            p1 = new BigNumber(p); p1.limbs[0] -= 1;
            return BigNumber_extGCD( p1, e ).gcd.valueOf() == 1;
        }
    );

    q = BigNumber_randomProbablePrime(
        bitlen - (bitlen >> 1),
        function ( q ) {
            m = new Modulus( p.multiply(q) );
            if ( !( m.limbs[ ( (bitlen + 31) >> 5 ) - 1 ] >>> ( (bitlen - 1) & 31) ) ) return false;
            q1 = new BigNumber(q); q1.limbs[0] -= 1;
            return BigNumber_extGCD( q1, e ).gcd.valueOf() == 1;
        }
    );

    d = new Modulus( p1.multiply(q1) ).inverse(e);

    dp = d.divide(p1).remainder,
    dq = d.divide(q1).remainder;

    p = new Modulus(p),
    q = new Modulus(q);

    var u = p.inverse(q);

    return [ m, e, d, p, q, dp, dq, u ];
}

RSA.generateKey = RSA_generateKey;
<|endoftext|>


<commit_before>import * as R from "ramda"

//

function Identity(value) {this.value = value}
const Ident = x => new Identity(x)
Identity.prototype.map = function (x2y) {return new Identity(x2y(this.value))}
Identity.prototype.of = Ident

//

function Constant(value) {this.value = value}
const Const = x => new Constant(x)
Constant.prototype.map = function () {return this}
Constant.prototype.of = Const

//

const warned = {}

const warn = message => {
  if (!(message in warned)) {
    warned[message] = message
    console.warn("partial.lenses:", message)
  }
}

//

const id = x => x
const snd = (_, c) => c

//

const check = (expected, predicate) => x => {
  if (predicate(x))
    return x
  else
    throw new Error(`Expected ${expected}, but got ${x}.`)
}

const assert = process.env.NODE_ENV === "production" ? () => id : check

//

const empty = {}

const deleteKey = (k, o) => {
  if (o === undefined || !(k in o))
    return o
  let r
  for (const p in o) {
    if (p !== k) {
      if (undefined === r)
        r = {}
      r[p] = o[p]
    }
  }
  return r
}

const setKey = (k, v, o) => {
  if (o === undefined)
    return {[k]: v}
  if (k in o && R.equals(v, o[k]))
    return o
  const r = {[k]: v}
  for (const p in o)
    if (p !== k)
      r[p] = o[p]
  return r
}

//

const dropped = xs => Object.keys(xs).length === 0 ? undefined : xs

//

const toPartial = transform => x => undefined === x ? x : transform(x)

//

const conserve = (c1, c0) => R.equals(c1, c0) ? c0 : c1

const toConserve = f => (y, c0) => conserve(f(y, c0), c0)

//

const seemsLens = x => typeof x === "function" && x.length === 1

export const fromRamda = assert("a lens", seemsLens)

export const toRamda = l => {
  if (isProp(l))  return toRamdaProp(l)
  if (isIndex(l)) return toRamdaIndex(l)
  return fromRamda(l)
}

export const compose = (...ls) =>
  ls.length === 0 ? identity :
  ls.length === 1 ? ls[0] :
  R.compose(...ls.map(toRamda))

export const remove = R.curry((l, s) => setI(toRamda(l), undefined, s))

export const removeAll = R.curry((lens, data) => {
  warn("`removeAll` is deprecated and there is no planned replacement --- use a different approach.")
  while (get(lens, data) !== undefined)
    data = remove(lens, data)
  return data
})

const setI = (l, x, s) => l(() => Ident(x))(s).value
const getI = (l, s) => l(Const)(s).value
const modifyI = (l, x2x, s) => l(y => Ident(x2x(y)))(s).value
const lensI = (getter, setter) => toFn => target =>
  toFn(getter(target)).map(focus => setter(focus, target))

export const lens = R.curry(lensI)
export const modify = R.curry((l, x2x, s) => modifyI(toRamda(l), x2x, s))
export const set = R.curry((l, x, s) => setI(toRamda(l), x, s))
export const get = R.curry((l, s) => getI(toRamda(l), s))

export const chain = R.curry((x2yL, xL) =>
  compose(xL, choose(xO => xO === undefined ? nothing : x2yL(xO))))

export const just = x => lensI(R.always(x), snd)

export const choose = x2yL => toFunctor => target => {
  const l = toRamda(x2yL(target))
  return R.map(focus => setI(l, focus, target), toFunctor(getI(l, target)))
}

export const nothing = lensI(snd, snd)

export const orElse =
  R.curry((d, l) => choose(x => getI(toRamda(l), x) !== undefined ? l : d))

export const choice = (...ls) => choose(x => {
  const i = ls.findIndex(l => getI(toRamda(l), x) !== undefined)
  return 0 <= i ? ls[i] : nothing
})

export const replace = R.curry((inn, out) =>
  lensI(x => R.equals(x, inn) ? out : x,
        toConserve(y => R.equals(y, out) ? inn : y)))

export const defaults = replace(undefined)
export const required = inn => replace(inn, undefined)
export const define = v => R.compose(required(v), defaults(v))

export const normalize = transform =>
  lensI(toPartial(transform), toConserve(toPartial(transform)))

const isProp = x => typeof x === "string"

export const prop = assert("a string", isProp)

const toRamdaProp = k =>
  lensI(o => o && o[k],
        (v, o) => v === undefined ? deleteKey(k, o) : setKey(k, v, o))

export const find = predicate => choose(xs => {
  if (xs === undefined)
    return append
  const i = xs.findIndex(predicate)
  return i < 0 ? append : i
})

export const findWith = (...ls) => {
  const lls = toRamda(compose(...ls))
  return compose(find(x => getI(lls, x) !== undefined), lls)
}

const isIndex = x => Number.isInteger(x) && 0 <= x

export const index = assert("a non-negative integer", isIndex)

const toRamdaIndex = i => lensI(xs => xs && xs[i], (x, xs) => {
  if (x === undefined) {
    if (xs === undefined)
      return undefined
    if (i < xs.length)
      return dropped(xs.slice(0, i).concat(xs.slice(i+1)))
    return xs
  } else {
    if (xs === undefined)
      return Array(i).concat([x])
    if (xs.length <= i)
      return xs.concat(Array(i - xs.length), [x])
    if (R.equals(x, xs[i]))
      return xs
    return xs.slice(0, i).concat([x], xs.slice(i+1))
  }
})

export const append = lensI(snd, (x, xs) =>
  x === undefined ? xs : xs === undefined ? [x] : xs.concat([x]))

export const filter = p => lensI(xs => xs && xs.filter(p), (ys, xs) =>
  conserve(dropped(R.concat(ys || [], (xs || []).filter(R.complement(p)))), xs))

export const augment = template => lensI(
  toPartial(x => {
    const z = {...x}
    for (const k in template)
      z[k] = template[k](x)
    return z
  }),
  toConserve((y, c) => {
    if (y === undefined)
      return undefined
    let z
    const set = (k, v) => {
      if (undefined === z)
        z = {}
      z[k] = v
    }
    for (const k in y) {
      if (!(k in template))
        set(k, y[k])
      else
        if (k in c)
          set(k, c[k])
    }
    return z
  }))

export const pick = template => lensI(
  c => {
    let r
    for (const k in template) {
      const v = getI(toRamda(template[k]), c)
      if (v !== undefined) {
        if (r === undefined)
          r = {}
        r[k] = v
      }
    }
    return r
  },
  (o = empty, cIn) => {
    let c = cIn
    for (const k in template)
      c = setI(toRamda(template[k]), o[k], c)
    return c
  })

export const identity = lensI(id, conserve)

export const props = (...ks) => pick(R.zipObj(ks, ks))

const show = (...labels) => x => console.log(...labels, x) || x

export const log = (...labels) =>
  lensI(show(...labels, "get"), show(...labels, "set"))

export default compose
<commit_msg>Adjust warning text.<commit_after>import * as R from "ramda"

//

function Identity(value) {this.value = value}
const Ident = x => new Identity(x)
Identity.prototype.map = function (x2y) {return new Identity(x2y(this.value))}
Identity.prototype.of = Ident

//

function Constant(value) {this.value = value}
const Const = x => new Constant(x)
Constant.prototype.map = function () {return this}
Constant.prototype.of = Const

//

const warned = {}

const warn = message => {
  if (!(message in warned)) {
    warned[message] = message
    console.warn("partial.lenses:", message)
  }
}

//

const id = x => x
const snd = (_, c) => c

//

const check = (expected, predicate) => x => {
  if (predicate(x))
    return x
  else
    throw new Error(`Expected ${expected}, but got ${x}.`)
}

const assert = process.env.NODE_ENV === "production" ? () => id : check

//

const empty = {}

const deleteKey = (k, o) => {
  if (o === undefined || !(k in o))
    return o
  let r
  for (const p in o) {
    if (p !== k) {
      if (undefined === r)
        r = {}
      r[p] = o[p]
    }
  }
  return r
}

const setKey = (k, v, o) => {
  if (o === undefined)
    return {[k]: v}
  if (k in o && R.equals(v, o[k]))
    return o
  const r = {[k]: v}
  for (const p in o)
    if (p !== k)
      r[p] = o[p]
  return r
}

//

const dropped = xs => Object.keys(xs).length === 0 ? undefined : xs

//

const toPartial = transform => x => undefined === x ? x : transform(x)

//

const conserve = (c1, c0) => R.equals(c1, c0) ? c0 : c1

const toConserve = f => (y, c0) => conserve(f(y, c0), c0)

//

const seemsLens = x => typeof x === "function" && x.length === 1

export const fromRamda = assert("a lens", seemsLens)

export const toRamda = l => {
  if (isProp(l))  return toRamdaProp(l)
  if (isIndex(l)) return toRamdaIndex(l)
  return fromRamda(l)
}

export const compose = (...ls) =>
  ls.length === 0 ? identity :
  ls.length === 1 ? ls[0] :
  R.compose(...ls.map(toRamda))

export const remove = R.curry((l, s) => setI(toRamda(l), undefined, s))

export const removeAll = R.curry((lens, data) => {
  warn("`removeAll` is deprecated and will be removed in next major version --- use a different approach.")
  while (get(lens, data) !== undefined)
    data = remove(lens, data)
  return data
})

const setI = (l, x, s) => l(() => Ident(x))(s).value
const getI = (l, s) => l(Const)(s).value
const modifyI = (l, x2x, s) => l(y => Ident(x2x(y)))(s).value
const lensI = (getter, setter) => toFn => target =>
  toFn(getter(target)).map(focus => setter(focus, target))

export const lens = R.curry(lensI)
export const modify = R.curry((l, x2x, s) => modifyI(toRamda(l), x2x, s))
export const set = R.curry((l, x, s) => setI(toRamda(l), x, s))
export const get = R.curry((l, s) => getI(toRamda(l), s))

export const chain = R.curry((x2yL, xL) =>
  compose(xL, choose(xO => xO === undefined ? nothing : x2yL(xO))))

export const just = x => lensI(R.always(x), snd)

export const choose = x2yL => toFunctor => target => {
  const l = toRamda(x2yL(target))
  return R.map(focus => setI(l, focus, target), toFunctor(getI(l, target)))
}

export const nothing = lensI(snd, snd)

export const orElse =
  R.curry((d, l) => choose(x => getI(toRamda(l), x) !== undefined ? l : d))

export const choice = (...ls) => choose(x => {
  const i = ls.findIndex(l => getI(toRamda(l), x) !== undefined)
  return 0 <= i ? ls[i] : nothing
})

export const replace = R.curry((inn, out) =>
  lensI(x => R.equals(x, inn) ? out : x,
        toConserve(y => R.equals(y, out) ? inn : y)))

export const defaults = replace(undefined)
export const required = inn => replace(inn, undefined)
export const define = v => R.compose(required(v), defaults(v))

export const normalize = transform =>
  lensI(toPartial(transform), toConserve(toPartial(transform)))

const isProp = x => typeof x === "string"

export const prop = assert("a string", isProp)

const toRamdaProp = k =>
  lensI(o => o && o[k],
        (v, o) => v === undefined ? deleteKey(k, o) : setKey(k, v, o))

export const find = predicate => choose(xs => {
  if (xs === undefined)
    return append
  const i = xs.findIndex(predicate)
  return i < 0 ? append : i
})

export const findWith = (...ls) => {
  const lls = toRamda(compose(...ls))
  return compose(find(x => getI(lls, x) !== undefined), lls)
}

const isIndex = x => Number.isInteger(x) && 0 <= x

export const index = assert("a non-negative integer", isIndex)

const toRamdaIndex = i => lensI(xs => xs && xs[i], (x, xs) => {
  if (x === undefined) {
    if (xs === undefined)
      return undefined
    if (i < xs.length)
      return dropped(xs.slice(0, i).concat(xs.slice(i+1)))
    return xs
  } else {
    if (xs === undefined)
      return Array(i).concat([x])
    if (xs.length <= i)
      return xs.concat(Array(i - xs.length), [x])
    if (R.equals(x, xs[i]))
      return xs
    return xs.slice(0, i).concat([x], xs.slice(i+1))
  }
})

export const append = lensI(snd, (x, xs) =>
  x === undefined ? xs : xs === undefined ? [x] : xs.concat([x]))

export const filter = p => lensI(xs => xs && xs.filter(p), (ys, xs) =>
  conserve(dropped(R.concat(ys || [], (xs || []).filter(R.complement(p)))), xs))

export const augment = template => lensI(
  toPartial(x => {
    const z = {...x}
    for (const k in template)
      z[k] = template[k](x)
    return z
  }),
  toConserve((y, c) => {
    if (y === undefined)
      return undefined
    let z
    const set = (k, v) => {
      if (undefined === z)
        z = {}
      z[k] = v
    }
    for (const k in y) {
      if (!(k in template))
        set(k, y[k])
      else
        if (k in c)
          set(k, c[k])
    }
    return z
  }))

export const pick = template => lensI(
  c => {
    let r
    for (const k in template) {
      const v = getI(toRamda(template[k]), c)
      if (v !== undefined) {
        if (r === undefined)
          r = {}
        r[k] = v
      }
    }
    return r
  },
  (o = empty, cIn) => {
    let c = cIn
    for (const k in template)
      c = setI(toRamda(template[k]), o[k], c)
    return c
  })

export const identity = lensI(id, conserve)

export const props = (...ks) => pick(R.zipObj(ks, ks))

const show = (...labels) => x => console.log(...labels, x) || x

export const log = (...labels) =>
  lensI(show(...labels, "get"), show(...labels, "set"))

export default compose
<|endoftext|>