<commit_before>//Copyright 2014, Small Picture, Inc.
	//Last update: 1/23/2014; 11:50:19 AM Eastern.

var myVersion = "0.45";

var s3HostingPath = process.env.fpHostingPath; //where we store all the files we create
var s3defaultType = "text/plain";
var s3defaultAcl = "public-read";

var s3DataPath = process.env.fpDataPath;
var s3NamesPath = s3DataPath + "names";
var s3StatsPath = s3DataPath + "stats";

var myDomain = process.env.fpDomain;

var http = require ("http");
var request = require ("request");
var urlpack = require ("url");
var AWS = require ("aws-sdk");
var s3 = new AWS.S3 ();

function httpReadUrl (url, callback) {
	request (url, function (error, response, body) {
		if (!error && response.statusCode == 200) {
			callback (body)
			}
		});

	}
function s3SplitPath (path) { //split path into bucketname and path -- like this: /tmp.scripting.com/testing/one.txt
	var bucketname = "";
	if (path.length > 0) {
		if (path [0] == "/") { //delete the slash
			path = path.substr (1);
			}
		var ix = path.indexOf ("/");
		bucketname = path.substr (0, ix);
		path = path.substr (ix + 1);
		}
	return ({Bucket: bucketname, Key: path});
	}
function s3NewObject (path, data, type, acl, callback) {
	var bucketname = "";
	if (type == undefined) {
		type = s3defaultType;
		}
	if (acl == undefined) {
		acl = s3defaultAcl;
		}

	//split path into bucketname and path -- like this: /tmp.scripting.com/testing/one.txt
		if (path.length > 0) {
			if (path [0] == "/") { //delete the slash
				path = path.substr (1);
				}
			var ix = path.indexOf ("/");
			bucketname = path.substr (0, ix);
			path = path.substr (ix + 1);
			}

	var params = {
		ACL: acl,
		ContentType: type,
		Body: data,
		Bucket: bucketname,
		Key: path
		};
	s3.putObject (params, function (err, data) {
		console.log ("s3NewObject: http://" + bucketname + "/" + path);
		if (callback != undefined) {
			callback (err, data);
			}
		});
	}
function s3GetObjectMetadata (path, callback) {
	var params = s3SplitPath (path);
	s3.headObject (params, function (err, data) {
		callback (data);
		});
	}
function s3GetObject (path, callback) {
	var params = s3SplitPath (path);
	s3.getObject (params, function (err, data) {
		callback (data);
		});
	}

function addNameRecord (name, opmlUrl, callback) { //add "dave" to give the name dave.smallpict.com to the outline
	var data = {
		"name": name,
		"opmlUrl": opmlUrl,
		"whenCreated": new Date ().toString ()
		};
	s3NewObject (s3NamesPath + "/" + name + ".json", JSON.stringify (data), "text/plain", "public-read", function (err, data) {
		callback (err, data);
		});
	}
function isNameDefined (name, callback) {
	s3GetObjectMetadata (s3NamesPath + "/" + name + ".json", function (metadata) {
		console.log ("isNameDefined: " + JSON.stringify (metadata));
		callback (metadata != null);
		});
	}
function getNameRecord (name, callback) {
	s3GetObject (s3NamesPath + "/" + name + ".json", function (err, data) {
		callback (data.Body);
		});
	}

function padWithZeros (num, ctplaces) {
	var s = num.toString ();
	while (s.length < ctplaces) {
		s = "0" + s;
		}
	return (s);
	}
function isAlpha (ch) {
	return (((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')));
	}
function isNumeric (ch) {
	return ((ch >= '0') && (ch <= '9'));
	}
function cleanName (name) {
	var s = "";
	for (var i = 0; i < name.length; i++) {
		var ch = name [i];
		if (isAlpha (ch) || isNumeric (ch)) {
			s += ch;
			}
		}
	return (s.toLowerCase (s));
	}
function scrapeTagValue (sourcestring, tagname) {
	var s = sourcestring; //work with a copy
	var opentag = "<" + tagname + ">", closetag = "</" + tagname + ">";
	var ix = s.indexOf (opentag);
	if (ix >= 0) {
		s = s.substr (ix + opentag.length);
		ix = s.indexOf (closetag);
		if (ix >= 0) {
			s = s.substr (0, ix);
			return (s);
			}
		}
	return ("");
	}
function parsePackages (s) {
	var magicpattern = "<[{~#--- ", ix, path, htmltext;
	while (s.length > 0) {
		ix = s.indexOf (magicpattern);
		if (ix < 0) {
			break;
			}
		s = s.substr (ix + magicpattern.length);
		ix = s.indexOf ("\n");
		path = s.substr (0, ix);
		s = s.substr (ix + 1);
		ix = s.indexOf (magicpattern);
		if (ix < 0) {
			htmltext = s;
			}
		else {
			htmltext = s.substr (0, ix);
			s = s.substr (ix);
			}
		console.log ("\"" + path + "\" == " + htmltext.length + " characters.");

		if (path.length > 0) {
			if (path [0] == "/") { //delete leading slash, if present
				path = path.substr (1);
				}
			s3NewObject (s3HostingPath + path, htmltext, "text/html");
			}
		}
	}
function handlePackagePing (urloutline) {
	console.log ("handlePackagePing: " + urloutline);
	httpReadUrl (urloutline, function (httptext) {
		var urlpackage = scrapeTagValue (httptext, "linkHosting");
		console.log ("package url: " + urlpackage);
		httpReadUrl (urlpackage, function (packagetext) {
			console.log ("package text: " + packagetext.length + " chars.");
			parsePackages (packagetext);
			});
		});
	}

console.log ("Fargo Publisher server v" + myVersion);

var server = http.createServer (function (httpRequest, httpResponse) {
	console.log (httpRequest.url);

	var parsedUrl = urlpack.parse (httpRequest.url, true);

	switch (parsedUrl.pathname.toLowerCase ()) {
		case "/pingpackage":
			httpResponse.writeHead (200, {"Content-Type": "application/json"});

			handlePackagePing (parsedUrl.query.link);

			var x = {"url": parsedUrl.query.link};
			var s = "getData (" + JSON.stringify (x) + ")";
			httpResponse.end (s);

			break;
		case "/isnameavailable":
			var name = cleanName (parsedUrl.query.name);

			httpResponse.writeHead (200, {"Content-Type": "text/html"});

			if (name.length == 0) {
				httpResponse.end ("");
				}
			else {
				if (name.length < 4) {
					httpResponse.end ("Name must be 4 or more characters.");
					}
				else {
					isNameDefined (name, function (fldefined) {
						var color, answer;
						if (fldefined) {
							color = "red";
							answer = "is not";
							}
						else {
							color = "green";
							answer = "is";
							}
						httpResponse.end ("" + name + "." + myDomain + " " + answer + " available.")
						});
					}
				}

			break;
		case "/newoutlinename":
			var recordkey = cleanName (parsedUrl.query.name), url = parsedUrl.query.url;

			console.log ("Create new outline name: " + recordkey + ", url=" + url);

			httpResponse.writeHead (200, {"Content-Type": "application/json"});

			if (url == undefined) {
				var x = {flError: true, errorString: "Can't assign the name because there is no url parameter provided."};
				console.log ("No url parameter.");
				httpResponse.end ("getData (" + JSON.stringify (x) + ")");
				}
			else {
				isNameDefined (recordkey, function (fldefined) {
					if (fldefined) {
						var x = {flError: true, errorString: "Can't assign the name '" + recordkey + "' to the outline because there already is an outline with that name."};
						console.log ("Name is defined.");
						httpResponse.end ("getData (" + JSON.stringify (x) + ")");
						}
					else {
						addNameRecord (recordkey, url, function (err, data) {
							if (err) {
								console.log ("There was an error: " + JSON.stringify (err));
								httpResponse.end ("getData (" + JSON.stringify (err) + ")");
								}
							else {
								var x = {flError: false, name: recordkey + "." + myDomain};
								console.log ("No error: " + recordkey + "." + myDomain);
								httpResponse.end ("getData (" + JSON.stringify (x) + ")");
								}
							});
						}
					});
				}
			break;
		case "/version":
			httpResponse.writeHead (200, {"Content-Type": "text/plain"});
			httpResponse.end (myVersion);
			break;
		default:
			httpResponse.writeHead (404, {"Content-Type": "text/plain"});
			httpResponse.end ("404 Not Found");
			break;
		}
	});
server.listen (1337);
<commit_msg>Update<commit_after>//Copyright 2014, Small Picture, Inc.
	//Last update: 1/23/2014; 12:09:43 PM Eastern.

var myVersion = "0.48";

var s3HostingPath = process.env.fpHostingPath; //where we store all the files we create
var s3defaultType = "text/plain";
var s3defaultAcl = "public-read";

var s3DataPath = process.env.fpDataPath;
var s3NamesPath = s3DataPath + "names";
var s3StatsPath = s3DataPath + "stats";

var myDomain = process.env.fpDomain;

var http = require ("http");
var request = require ("request");
var urlpack = require ("url");
var AWS = require ("aws-sdk");
var s3 = new AWS.S3 ();

function httpReadUrl (url, callback) {
	request (url, function (error, response, body) {
		if (!error && response.statusCode == 200) {
			callback (body)
			}
		});

	}
function s3SplitPath (path) { //split path into bucketname and path -- like this: /tmp.scripting.com/testing/one.txt
	var bucketname = "";
	if (path.length > 0) {
		if (path [0] == "/") { //delete the slash
			path = path.substr (1);
			}
		var ix = path.indexOf ("/");
		bucketname = path.substr (0, ix);
		path = path.substr (ix + 1);
		}
	return ({Bucket: bucketname, Key: path});
	}
function s3NewObject (path, data, type, acl, callback) {
	var bucketname = "";
	if (type == undefined) {
		type = s3defaultType;
		}
	if (acl == undefined) {
		acl = s3defaultAcl;
		}

	//split path into bucketname and path -- like this: /tmp.scripting.com/testing/one.txt
		if (path.length > 0) {
			if (path [0] == "/") { //delete the slash
				path = path.substr (1);
				}
			var ix = path.indexOf ("/");
			bucketname = path.substr (0, ix);
			path = path.substr (ix + 1);
			}

	var params = {
		ACL: acl,
		ContentType: type,
		Body: data,
		Bucket: bucketname,
		Key: path
		};
	s3.putObject (params, function (err, data) {
		console.log ("s3NewObject: http://" + bucketname + "/" + path);
		if (callback != undefined) {
			callback (err, data);
			}
		});
	}
function s3GetObjectMetadata (path, callback) {
	var params = s3SplitPath (path);
	s3.headObject (params, function (err, data) {
		callback (data);
		});
	}
function s3GetObject (path, callback) {
	var params = s3SplitPath (path);
	s3.getObject (params, function (err, data) {
		callback (data);
		});
	}

function addNameRecord (name, opmlUrl, callback) { //add "dave" to give the name dave.smallpict.com to the outline
	var data = {
		"name": name,
		"opmlUrl": opmlUrl,
		"whenCreated": new Date ().toString ()
		};
	s3NewObject (s3NamesPath + "/" + name + ".json", JSON.stringify (data), "text/plain", "public-read", function (err, data) {
		callback (err, data);
		});
	}
function isNameDefined (name, callback) {
	s3GetObjectMetadata (s3NamesPath + "/" + name + ".json", function (metadata) {
		console.log ("isNameDefined: " + JSON.stringify (metadata));
		callback (metadata != null);
		});
	}
function getNameRecord (name, callback) {
	s3GetObject (s3NamesPath + "/" + name + ".json", function (data) {
		callback (data.Body);
		});
	}

function padWithZeros (num, ctplaces) {
	var s = num.toString ();
	while (s.length < ctplaces) {
		s = "0" + s;
		}
	return (s);
	}
function isAlpha (ch) {
	return (((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')));
	}
function isNumeric (ch) {
	return ((ch >= '0') && (ch <= '9'));
	}
function cleanName (name) {
	var s = "";
	for (var i = 0; i < name.length; i++) {
		var ch = name [i];
		if (isAlpha (ch) || isNumeric (ch)) {
			s += ch;
			}
		}
	return (s.toLowerCase (s));
	}
function scrapeTagValue (sourcestring, tagname) {
	var s = sourcestring; //work with a copy
	var opentag = "<" + tagname + ">", closetag = "</" + tagname + ">";
	var ix = s.indexOf (opentag);
	if (ix >= 0) {
		s = s.substr (ix + opentag.length);
		ix = s.indexOf (closetag);
		if (ix >= 0) {
			s = s.substr (0, ix);
			return (s);
			}
		}
	return ("");
	}
function parsePackages (name, s) { //name is something like "dave"
	var magicpattern = "<[{~#--- ", ix, path, htmltext;
	while (s.length > 0) {
		ix = s.indexOf (magicpattern);
		if (ix < 0) {
			break;
			}
		s = s.substr (ix + magicpattern.length);
		ix = s.indexOf ("\n");
		path = s.substr (0, ix);
		s = s.substr (ix + 1);
		ix = s.indexOf (magicpattern);
		if (ix < 0) {
			htmltext = s;
			}
		else {
			htmltext = s.substr (0, ix);
			s = s.substr (ix);
			}
		console.log ("\"" + path + "\" == " + htmltext.length + " characters.");

		if (path.length > 0) {
			if (path [0] == "/") { //delete leading slash, if present
				path = path.substr (1);
				}
			s3NewObject (s3HostingPath + name + "/" + path, htmltext, "text/html");
			}
		}
	}
function handlePackagePing (subdomain) { //something like dave.smallpict.com
	var sections = subdomain.split (".");
	var name = sections [0];

	console.log ("handlePackagePing: " + name);

	getNameRecord (name, function (jsontext) {
		var obj = JSON.parse (jsontext);
		console.log ("handlePackagePing: jsontext == " + jsontext);
		httpReadUrl (obj.opmlUrl, function (httptext) {
			var urlpackage = scrapeTagValue (httptext, "linkHosting");
			console.log ("handlePackagePing: package url == " + urlpackage);
			httpReadUrl (urlpackage, function (packagetext) {
				console.log ("package text: " + packagetext.length + " chars.");
				parsePackages (name, packagetext);
				});
			});
		});

	}

console.log ("Fargo Publisher server v" + myVersion);

var server = http.createServer (function (httpRequest, httpResponse) {
	console.log (httpRequest.url);

	var parsedUrl = urlpack.parse (httpRequest.url, true);

	switch (parsedUrl.pathname.toLowerCase ()) {
		case "/pingpackage":
			httpResponse.writeHead (200, {"Content-Type": "application/json"});

			handlePackagePing (parsedUrl.query.name);

			var x = {"name": parsedUrl.query.name};
			var s = "getData (" + JSON.stringify (x) + ")";
			httpResponse.end (s);

			break;
		case "/isnameavailable":
			var name = cleanName (parsedUrl.query.name);

			httpResponse.writeHead (200, {"Content-Type": "text/html"});

			if (name.length == 0) {
				httpResponse.end ("");
				}
			else {
				if (name.length < 4) {
					httpResponse.end ("Name must be 4 or more characters.");
					}
				else {
					isNameDefined (name, function (fldefined) {
						var color, answer;
						if (fldefined) {
							color = "red";
							answer = "is not";
							}
						else {
							color = "green";
							answer = "is";
							}
						httpResponse.end ("" + name + "." + myDomain + " " + answer + " available.")
						});
					}
				}

			break;
		case "/newoutlinename":
			var recordkey = cleanName (parsedUrl.query.name), url = parsedUrl.query.url;

			console.log ("Create new outline name: " + recordkey + ", url=" + url);

			httpResponse.writeHead (200, {"Content-Type": "application/json"});

			if (url == undefined) {
				var x = {flError: true, errorString: "Can't assign the name because there is no url parameter provided."};
				console.log ("No url parameter.");
				httpResponse.end ("getData (" + JSON.stringify (x) + ")");
				}
			else {
				isNameDefined (recordkey, function (fldefined) {
					if (fldefined) {
						var x = {flError: true, errorString: "Can't assign the name '" + recordkey + "' to the outline because there already is an outline with that name."};
						console.log ("Name is defined.");
						httpResponse.end ("getData (" + JSON.stringify (x) + ")");
						}
					else {
						addNameRecord (recordkey, url, function (err, data) {
							if (err) {
								console.log ("There was an error: " + JSON.stringify (err));
								httpResponse.end ("getData (" + JSON.stringify (err) + ")");
								}
							else {
								var x = {flError: false, name: recordkey + "." + myDomain};
								console.log ("No error: " + recordkey + "." + myDomain);
								httpResponse.end ("getData (" + JSON.stringify (x) + ")");
								}
							});
						}
					});
				}
			break;
		case "/version":
			httpResponse.writeHead (200, {"Content-Type": "text/plain"});
			httpResponse.end (myVersion);
			break;
		default:
			httpResponse.writeHead (404, {"Content-Type": "text/plain"});
			httpResponse.end ("404 Not Found");
			break;
		}
	});
server.listen (1337);
<|endoftext|>


<commit_before>'use strict';
/**
 * Github FileBrowser configuration module.
 */

var m = require('mithril');
var $ = require('jquery');
var URI = require('URIjs');
var Fangorn = require('js/fangorn');
var waterbutler = require('js/waterbutler');
var $osf = require('js/osfHelpers');

// Cross browser key codes for the Command key
var commandKeys = [224, 17, 91, 93];

function _uploadUrl(item, file) {
    return waterbutler.buildTreeBeardUpload(item, file, {branch: item.data.branch});
}

function _removeEvent (event, items) {
    var tb = this;
    function cancelDelete() {
        tb.modal.dismiss();
    }

    function runDelete (item) {
        // delete from server, if successful delete from view
        tb.select('.modal-footer .btn-danger').html(' Deleting...').removeClass('btn-danger').addClass('btn-default disabled');

        $.ajax({
            url: waterbutler.buildTreeBeardDelete(item, {branch: item.data.branch, sha: item.data.extra.fileSha}),
            type : 'DELETE',
            beforeSend: $osf.setXHRAuthorization
        }).done(function (data) {
                // delete view
                tb.deleteNode(item.parentID, item.id);
                Fangorn.Utils.dismissToolbar.call(tb);
                tb.modal.dismiss();
                tb.clearMultiselect();

        }).fail(function (data) {
                tb.modal.dismiss();
                Fangorn.Utils.dismissToolbar.call(tb);
                item.notify.update('Delete failed.', 'danger', undefined, 3000);
                tb.clearMultiselect();
        });
    }

    function runDeleteMultiple(items){
        items.forEach(function(item){
            runDelete(item);
        });
    }

    // If there is only one item being deleted, don't complicate the issue:
    if(items.length === 1) {
        var parent = items[0].parent();
        var mithrilContentSingle = m('div', [
            m('p', 'This action is irreversible.'),
            parent.children.length < 2 ? m('p', 'If a folder in Github has no children it will automatically be removed.') : ''
        ]);
        var mithrilButtonsSingle = m('div', [
            m('span.btn.btn-default', { onclick : function() { cancelDelete(); } }, 'Cancel'),
            m('span.btn.btn-danger', { onclick : function() { runDelete(items[0]); }  }, 'Delete')
        ]);
        // This is already being checked before this step but will keep this edit permission check
        if(items[0].data.permissions.edit){
            tb.modal.update(mithrilContentSingle, mithrilButtonsSingle, m('h3.break-word.modal-title', 'Delete "' + items[0].data.name + '"?'));
        }
    } else {
        // Check if all items can be deleted
        var canDelete = true;
        var deleteList = [];
        var noDeleteList = [];
        var mithrilContentMultiple;
        var mithrilButtonsMultiple;
        items.forEach(function(item, index, arr){
            if(!item.data.permissions.edit){
                canDelete = false;
                noDeleteList.push(item);
            } else {
                deleteList.push(item);
            }
        });
        // If all items can be deleted
        if(canDelete){
            mithrilContentMultiple = m('div', [
                    m('p', 'This action is irreversible.'),
                    deleteList.map(function(item){
                        return m('.fangorn-canDelete.text-success', item.data.name);
                    })
                ]);
            mithrilButtonsMultiple =  m('div', [
                    m('span.btn.btn-default', { 'class' : 'text-default', onclick : function() { cancelDelete(); } }, 'Cancel'),
                    m('span.btn.btn-danger', {  'class' : 'text-danger', onclick : function() { runDeleteMultiple.call(tb, deleteList); }  }, 'Delete All')
                ]);
        } else {
            mithrilContentMultiple = m('div', [
                    m('p', 'Some of these files can\'t be deleted but you can delete the ones highlighted with green. This action is irreversible.'),
                    deleteList.map(function(n){
                        return m('.fangorn-canDelete.text-success', n.data.name);
                    }),
                    noDeleteList.map(function(n){
                        return m('.fangorn-noDelete.text-warning', n.data.name);
                    })
                ]);
            mithrilButtonsMultiple =  m('div', [
                    m('span.btn.btn-default', { 'class' : 'text-default', onclick : function() { cancelDelete(); } }, 'Cancel'),
                    m('span.btn.btn-danger', { 'class' : 'text-danger', onclick : function() { runDeleteMultiple.call(tb, deleteList); }  }, 'Delete Some')
                ]);
        }
        tb.modal.update(mithrilContentMultiple, mithrilButtonsMultiple, m('h3.break-word.modal-title', 'Delete multiple files?'));
    }

    return true; // Let fangorn know this config option was used.
}

// Define Fangorn Button Actions
var _githubItemButtons = {
    view: function (ctrl, args, children) {
        var tb = args.treebeard;
        var item = args.item;
        var buttons = [];
        function _downloadEvent(event, item, col) {
            event.stopPropagation();
            window.location = waterbutler.buildTreeBeardDownload(item, {fileSha: item.data.extra.fileSha});
        }
        // Download Zip File
        if (item.kind === 'folder') {
            var branchArray = [];
            if (item.data.branches) {
                item.data.branch = item.data.branch || item.data.defaultBranch;
                for (var i = 0; i < item.data.branches.length; i++) {
                    var selected = item.data.branches[i] === item.data.branch ? 'selected' : '';
                    branchArray.push(m('option', {
                        selected: selected,
                        value: item.data.branches[i]
                    }, item.data.branches[i]));
                }
            }
            if (item.data.addonFullname) {
                buttons.push(
                    m.component(Fangorn.Components.dropdown, {
                        'label': 'Branch: ',
                        onchange: function (event) {
                            changeBranch.call(tb, item, event.target.value);
                        },
                        icon: 'fa fa-external-link',
                        className: 'text-info'
                    }, branchArray)
                );
            }
            if (tb.options.placement !== 'fileview') {
                // If File and FileRead are not defined dropzone is not supported and neither is uploads
                if (window.File && window.FileReader && item.data.permissions && item.data.permissions.edit) {
                    buttons.push(
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                Fangorn.ButtonEvents._uploadEvent.call(tb, event, item);
                            },
                            icon: 'fa fa-upload',
                            className: 'text-success'
                        }, 'Upload'),
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                tb.toolbarMode(Fangorn.Components.toolbarModes.ADDFOLDER);
                            },
                            icon: 'fa fa-plus',
                            className: 'text-success'
                        }, 'Create Folder')
                    );
                    if(!item.data.isAddonRoot){
                        buttons.push(m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                _removeEvent.call(tb, event, [item]);
                            },
                            icon: 'fa fa-trash',
                            className: 'text-danger'
                        }, 'Delete Folder'));
                    }
                }
                if (item.data.addonFullname) {
                    buttons.push(
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                window.location = item.data.urls.zip;
                            },
                            icon: 'fa fa-download',
                            className: 'text-primary'
                        }, 'Download'),
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                window.open(item.data.urls.repo, '_blank');
                            },
                            icon: 'fa fa-external-link',
                            className: 'text-info'
                        }, 'Open')
                    );
                }
            }
        } else if (item.kind === 'file' && tb.options.placement !== 'fileview') {
            buttons.push(
                m.component(Fangorn.Components.button, {
                    onclick: function (event) {
                        _downloadEvent.call(tb, event, item);
                    },
                    icon: 'fa fa-download',
                    className: 'text-primary'
                }, 'Download')
            );
            if (item.data.permissions && item.data.permissions.view) {
                buttons.push(
                    m.component(Fangorn.Components.button, {
                        onclick: function(event) {
                            gotoFile.call(tb, item);
                        },
                        icon: 'fa fa-file-o',
                        className : 'text-info'
                    }, 'View'));
            }
            if (item.data.permissions && item.data.permissions.edit) {
                buttons.push(
                    m.component(Fangorn.Components.button, {
                        onclick: function (event) {
                            _removeEvent.call(tb, event, [item]);
                        },
                        icon: 'fa fa-trash',
                        className: 'text-danger'
                    }, 'Delete')
                );
            }
            if (item.data.permissions && item.data.permissions.view && !item.data.permissions.private) {
                buttons.push(
                    m('a.text-info.fangorn-toolbar-icon', {href: item.data.extra.webView}, [
                        m('i.fa.fa-external-link'),
                        m('span', 'View on GitHub')
                    ])
                );
            }
        }

        if(item.data.provider && !item.data.isAddonRoot && item.data.permissions && item.data.permissions.edit && tb.options.placement !== 'fileview') {
            buttons.push(
                m.component(Fangorn.Components.button, {
                    onclick: function() {
                        tb.toolbarMode(Fangorn.Components.toolbarModes.RENAME);
                    },
                    tooltip: 'Change the name of the item',
                    icon: 'fa fa-font',
                    className : 'text-info'
                }, 'Rename')
            );
        }

        return m('span', buttons); // Tell fangorn this function is used.
    }
};

function changeBranch(item, ref){
    item.data.branch = ref;
    this.updateFolder(null, item);
}

function _resolveLazyLoad(item) {
    return waterbutler.buildTreeBeardMetadata(item, {ref: item.data.branch});
}

function _fangornLazyLoadOnLoad (tree, event) {
    var tb = this;
    tree.children.forEach(function(item) {
        Fangorn.Utils.inheritFromParent(item, tree, ['branch']);
    });
    Fangorn.Utils.setCurrentFileID.call(tb, tree, window.contextVars.node.id, window.contextVars.file);
    if(!event && tb.isMultiselected(tb.currentFileID)){
        Fangorn.Utils.scrollToFile.call(tb, tb.currentFileID);
    }
}

function gotoFile (item) {
    var tb = this;
    var fileurl = new URI(item.data.nodeUrl)
        .segment('files')
        .segment(item.data.provider)
        .segment(item.data.path.substring(1))
        .search({branch: item.data.branch})
        .toString();
    if(commandKeys.indexOf(tb.pressedKey) !== -1) {
        window.open(fileurl, '_blank');
    } else {
        window.open(fileurl, '_self');
    }
}

function _fangornGithubTitle(item, col)  {
    var tb = this;
    if (item.data.isAddonRoot && item.connected === false) { // as opposed to undefined, avoids unnecessary setting of this value
        return Fangorn.Utils.connectCheckTemplate.call(this, item);
    }

    if (item.data.addonFullname) {
        var urlParams = $osf.urlParams();

        if (!item.data.branch && urlParams.branch) {
            item.data.branch = urlParams.branch;
        }
        var branch = item.data.branch || item.data.defaultBranch;

        return m('span',[
            m('github-name', item.data.name + ' (' + branch + ')')
        ]);
    } else {
        if (item.kind === 'file' && item.data.permissions.view) {
            return m('span',[
                m('github-name.fg-file-links', {
                    onclick: function() {
                        gotoFile.call(tb, item);
                    }
                }, item.data.name)]);
        } else {
            return m('span', item.data.name);
        }
    }
}

function _fangornColumns (item) {
    var tb = this;
    var node = item.parent().parent();
    var columns = [];
    columns.push({
        data : 'name',
        folderIcons : true,
        filter: true,
        custom : _fangornGithubTitle
    });

    if(tb.options.placement === 'project-files') {
        columns.push(
        {
            data  : 'size',
            sortInclude : false,
            filter : false,
            custom : function() {return item.data.size ? $osf.humanFileSize(item.data.size, true) : '';}
        });
    }

    if(tb.options.placement === 'project-files') {
        columns.push(
        {
            data  : 'downloads',
            sortInclude : false,
            filter : false,
            custom : function() {return m('');}
        });
    }
    columns.push({
        data : 'modified',
        filter: false,
        custom : function() {return m('');}
    });
    return columns;
}

function _fangornFolderIcons(item){
    if(item.data.iconUrl){
        return m('img',{src:item.data.iconUrl, style:{width:'16px', height:'auto'}}, ' ');
    }
    return undefined;
}

function _fangornUploadComplete(item){
    var index = this.returnIndex(item.id);
}

function _fangornUploadSuccess(file, item, response) {
    if (response) {
        response.branch = item.parent().data.branch;
    }
}

// Register configuration
Fangorn.config.github = {
    // Handle changing the branch select
    uploadUrl: _uploadUrl,
    lazyload: _resolveLazyLoad,
    resolveRows: _fangornColumns,
    folderIcon: _fangornFolderIcons,
    onUploadComplete: _fangornUploadComplete,
    lazyLoadOnLoad: _fangornLazyLoadOnLoad,
    uploadSuccess: _fangornUploadSuccess,
    itemButtons: _githubItemButtons,
    removeEvent : _removeEvent
};
<commit_msg>Change github file widget rename icon to pencil [OSF-6465] (#5863)<commit_after>'use strict';
/**
 * Github FileBrowser configuration module.
 */

var m = require('mithril');
var $ = require('jquery');
var URI = require('URIjs');
var Fangorn = require('js/fangorn');
var waterbutler = require('js/waterbutler');
var $osf = require('js/osfHelpers');

// Cross browser key codes for the Command key
var commandKeys = [224, 17, 91, 93];

function _uploadUrl(item, file) {
    return waterbutler.buildTreeBeardUpload(item, file, {branch: item.data.branch});
}

function _removeEvent (event, items) {
    var tb = this;
    function cancelDelete() {
        tb.modal.dismiss();
    }

    function runDelete (item) {
        // delete from server, if successful delete from view
        tb.select('.modal-footer .btn-danger').html(' Deleting...').removeClass('btn-danger').addClass('btn-default disabled');

        $.ajax({
            url: waterbutler.buildTreeBeardDelete(item, {branch: item.data.branch, sha: item.data.extra.fileSha}),
            type : 'DELETE',
            beforeSend: $osf.setXHRAuthorization
        }).done(function (data) {
                // delete view
                tb.deleteNode(item.parentID, item.id);
                Fangorn.Utils.dismissToolbar.call(tb);
                tb.modal.dismiss();
                tb.clearMultiselect();

        }).fail(function (data) {
                tb.modal.dismiss();
                Fangorn.Utils.dismissToolbar.call(tb);
                item.notify.update('Delete failed.', 'danger', undefined, 3000);
                tb.clearMultiselect();
        });
    }

    function runDeleteMultiple(items){
        items.forEach(function(item){
            runDelete(item);
        });
    }

    // If there is only one item being deleted, don't complicate the issue:
    if(items.length === 1) {
        var parent = items[0].parent();
        var mithrilContentSingle = m('div', [
            m('p', 'This action is irreversible.'),
            parent.children.length < 2 ? m('p', 'If a folder in Github has no children it will automatically be removed.') : ''
        ]);
        var mithrilButtonsSingle = m('div', [
            m('span.btn.btn-default', { onclick : function() { cancelDelete(); } }, 'Cancel'),
            m('span.btn.btn-danger', { onclick : function() { runDelete(items[0]); }  }, 'Delete')
        ]);
        // This is already being checked before this step but will keep this edit permission check
        if(items[0].data.permissions.edit){
            tb.modal.update(mithrilContentSingle, mithrilButtonsSingle, m('h3.break-word.modal-title', 'Delete "' + items[0].data.name + '"?'));
        }
    } else {
        // Check if all items can be deleted
        var canDelete = true;
        var deleteList = [];
        var noDeleteList = [];
        var mithrilContentMultiple;
        var mithrilButtonsMultiple;
        items.forEach(function(item, index, arr){
            if(!item.data.permissions.edit){
                canDelete = false;
                noDeleteList.push(item);
            } else {
                deleteList.push(item);
            }
        });
        // If all items can be deleted
        if(canDelete){
            mithrilContentMultiple = m('div', [
                    m('p', 'This action is irreversible.'),
                    deleteList.map(function(item){
                        return m('.fangorn-canDelete.text-success', item.data.name);
                    })
                ]);
            mithrilButtonsMultiple =  m('div', [
                    m('span.btn.btn-default', { 'class' : 'text-default', onclick : function() { cancelDelete(); } }, 'Cancel'),
                    m('span.btn.btn-danger', {  'class' : 'text-danger', onclick : function() { runDeleteMultiple.call(tb, deleteList); }  }, 'Delete All')
                ]);
        } else {
            mithrilContentMultiple = m('div', [
                    m('p', 'Some of these files can\'t be deleted but you can delete the ones highlighted with green. This action is irreversible.'),
                    deleteList.map(function(n){
                        return m('.fangorn-canDelete.text-success', n.data.name);
                    }),
                    noDeleteList.map(function(n){
                        return m('.fangorn-noDelete.text-warning', n.data.name);
                    })
                ]);
            mithrilButtonsMultiple =  m('div', [
                    m('span.btn.btn-default', { 'class' : 'text-default', onclick : function() { cancelDelete(); } }, 'Cancel'),
                    m('span.btn.btn-danger', { 'class' : 'text-danger', onclick : function() { runDeleteMultiple.call(tb, deleteList); }  }, 'Delete Some')
                ]);
        }
        tb.modal.update(mithrilContentMultiple, mithrilButtonsMultiple, m('h3.break-word.modal-title', 'Delete multiple files?'));
    }

    return true; // Let fangorn know this config option was used.
}

// Define Fangorn Button Actions
var _githubItemButtons = {
    view: function (ctrl, args, children) {
        var tb = args.treebeard;
        var item = args.item;
        var buttons = [];
        function _downloadEvent(event, item, col) {
            event.stopPropagation();
            window.location = waterbutler.buildTreeBeardDownload(item, {fileSha: item.data.extra.fileSha});
        }
        // Download Zip File
        if (item.kind === 'folder') {
            var branchArray = [];
            if (item.data.branches) {
                item.data.branch = item.data.branch || item.data.defaultBranch;
                for (var i = 0; i < item.data.branches.length; i++) {
                    var selected = item.data.branches[i] === item.data.branch ? 'selected' : '';
                    branchArray.push(m('option', {
                        selected: selected,
                        value: item.data.branches[i]
                    }, item.data.branches[i]));
                }
            }
            if (item.data.addonFullname) {
                buttons.push(
                    m.component(Fangorn.Components.dropdown, {
                        'label': 'Branch: ',
                        onchange: function (event) {
                            changeBranch.call(tb, item, event.target.value);
                        },
                        icon: 'fa fa-external-link',
                        className: 'text-info'
                    }, branchArray)
                );
            }
            if (tb.options.placement !== 'fileview') {
                // If File and FileRead are not defined dropzone is not supported and neither is uploads
                if (window.File && window.FileReader && item.data.permissions && item.data.permissions.edit) {
                    buttons.push(
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                Fangorn.ButtonEvents._uploadEvent.call(tb, event, item);
                            },
                            icon: 'fa fa-upload',
                            className: 'text-success'
                        }, 'Upload'),
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                tb.toolbarMode(Fangorn.Components.toolbarModes.ADDFOLDER);
                            },
                            icon: 'fa fa-plus',
                            className: 'text-success'
                        }, 'Create Folder')
                    );
                    if(!item.data.isAddonRoot){
                        buttons.push(m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                _removeEvent.call(tb, event, [item]);
                            },
                            icon: 'fa fa-trash',
                            className: 'text-danger'
                        }, 'Delete Folder'));
                    }
                }
                if (item.data.addonFullname) {
                    buttons.push(
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                window.location = item.data.urls.zip;
                            },
                            icon: 'fa fa-download',
                            className: 'text-primary'
                        }, 'Download'),
                        m.component(Fangorn.Components.button, {
                            onclick: function (event) {
                                window.open(item.data.urls.repo, '_blank');
                            },
                            icon: 'fa fa-external-link',
                            className: 'text-info'
                        }, 'Open')
                    );
                }
            }
        } else if (item.kind === 'file' && tb.options.placement !== 'fileview') {
            buttons.push(
                m.component(Fangorn.Components.button, {
                    onclick: function (event) {
                        _downloadEvent.call(tb, event, item);
                    },
                    icon: 'fa fa-download',
                    className: 'text-primary'
                }, 'Download')
            );
            if (item.data.permissions && item.data.permissions.view) {
                buttons.push(
                    m.component(Fangorn.Components.button, {
                        onclick: function(event) {
                            gotoFile.call(tb, item);
                        },
                        icon: 'fa fa-file-o',
                        className : 'text-info'
                    }, 'View'));
            }
            if (item.data.permissions && item.data.permissions.edit) {
                buttons.push(
                    m.component(Fangorn.Components.button, {
                        onclick: function (event) {
                            _removeEvent.call(tb, event, [item]);
                        },
                        icon: 'fa fa-trash',
                        className: 'text-danger'
                    }, 'Delete')
                );
            }
            if (item.data.permissions && item.data.permissions.view && !item.data.permissions.private) {
                buttons.push(
                    m('a.text-info.fangorn-toolbar-icon', {href: item.data.extra.webView}, [
                        m('i.fa.fa-external-link'),
                        m('span', 'View on GitHub')
                    ])
                );
            }
        }

        if(item.data.provider && !item.data.isAddonRoot && item.data.permissions && item.data.permissions.edit && tb.options.placement !== 'fileview') {
            buttons.push(
                m.component(Fangorn.Components.button, {
                    onclick: function() {
                        tb.toolbarMode(Fangorn.Components.toolbarModes.RENAME);
                    },
                    tooltip: 'Change the name of the item',
                    icon: 'fa fa-pencil',
                    className : 'text-info'
                }, 'Rename')
            );
        }

        return m('span', buttons); // Tell fangorn this function is used.
    }
};

function changeBranch(item, ref){
    item.data.branch = ref;
    this.updateFolder(null, item);
}

function _resolveLazyLoad(item) {
    return waterbutler.buildTreeBeardMetadata(item, {ref: item.data.branch});
}

function _fangornLazyLoadOnLoad (tree, event) {
    var tb = this;
    tree.children.forEach(function(item) {
        Fangorn.Utils.inheritFromParent(item, tree, ['branch']);
    });
    Fangorn.Utils.setCurrentFileID.call(tb, tree, window.contextVars.node.id, window.contextVars.file);
    if(!event && tb.isMultiselected(tb.currentFileID)){
        Fangorn.Utils.scrollToFile.call(tb, tb.currentFileID);
    }
}

function gotoFile (item) {
    var tb = this;
    var fileurl = new URI(item.data.nodeUrl)
        .segment('files')
        .segment(item.data.provider)
        .segment(item.data.path.substring(1))
        .search({branch: item.data.branch})
        .toString();
    if(commandKeys.indexOf(tb.pressedKey) !== -1) {
        window.open(fileurl, '_blank');
    } else {
        window.open(fileurl, '_self');
    }
}

function _fangornGithubTitle(item, col)  {
    var tb = this;
    if (item.data.isAddonRoot && item.connected === false) { // as opposed to undefined, avoids unnecessary setting of this value
        return Fangorn.Utils.connectCheckTemplate.call(this, item);
    }

    if (item.data.addonFullname) {
        var urlParams = $osf.urlParams();

        if (!item.data.branch && urlParams.branch) {
            item.data.branch = urlParams.branch;
        }
        var branch = item.data.branch || item.data.defaultBranch;

        return m('span',[
            m('github-name', item.data.name + ' (' + branch + ')')
        ]);
    } else {
        if (item.kind === 'file' && item.data.permissions.view) {
            return m('span',[
                m('github-name.fg-file-links', {
                    onclick: function() {
                        gotoFile.call(tb, item);
                    }
                }, item.data.name)]);
        } else {
            return m('span', item.data.name);
        }
    }
}

function _fangornColumns (item) {
    var tb = this;
    var node = item.parent().parent();
    var columns = [];
    columns.push({
        data : 'name',
        folderIcons : true,
        filter: true,
        custom : _fangornGithubTitle
    });

    if(tb.options.placement === 'project-files') {
        columns.push(
        {
            data  : 'size',
            sortInclude : false,
            filter : false,
            custom : function() {return item.data.size ? $osf.humanFileSize(item.data.size, true) : '';}
        });
    }

    if(tb.options.placement === 'project-files') {
        columns.push(
        {
            data  : 'downloads',
            sortInclude : false,
            filter : false,
            custom : function() {return m('');}
        });
    }
    columns.push({
        data : 'modified',
        filter: false,
        custom : function() {return m('');}
    });
    return columns;
}

function _fangornFolderIcons(item){
    if(item.data.iconUrl){
        return m('img',{src:item.data.iconUrl, style:{width:'16px', height:'auto'}}, ' ');
    }
    return undefined;
}

function _fangornUploadComplete(item){
    var index = this.returnIndex(item.id);
}

function _fangornUploadSuccess(file, item, response) {
    if (response) {
        response.branch = item.parent().data.branch;
    }
}

// Register configuration
Fangorn.config.github = {
    // Handle changing the branch select
    uploadUrl: _uploadUrl,
    lazyload: _resolveLazyLoad,
    resolveRows: _fangornColumns,
    folderIcon: _fangornFolderIcons,
    onUploadComplete: _fangornUploadComplete,
    lazyLoadOnLoad: _fangornLazyLoadOnLoad,
    uploadSuccess: _fangornUploadSuccess,
    itemButtons: _githubItemButtons,
    removeEvent : _removeEvent
};
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var EventEmitter = require('./lib/event_emitter');
var inherits = require('util').inherits;
var util = require('util');
var errors = require('./errors');
var coerceRequestHandler = require('./request-handler');

function TChannelEndpointHandler(serviceName) {
    if (!(this instanceof TChannelEndpointHandler)) {
        return new TChannelEndpointHandler(serviceName);
    }
    var self = this;
    EventEmitter.call(self);
    self.handleEndpointEvent = self.defineEvent('handleEndpoint');

    self.serviceName = serviceName;
    self.endpoints = Object.create(null);
}
inherits(TChannelEndpointHandler, EventEmitter);

TChannelEndpointHandler.prototype.type = 'tchannel.endpoint-handler';

TChannelEndpointHandler.prototype.register = function register(name, options, handler) {
    var self = this;
    if (typeof options === 'function') {
        handler = options;
        options = {};
    }
    if (typeof handler !== 'function') {
        throw errors.InvalidHandlerError();
    }
    if (options.streamed) handler.canStream = true;
    self.endpoints[name] = coerceRequestHandler(handler, self, options);
    return handler;
};

TChannelEndpointHandler.prototype.handleRequest = function handleRequest(req, buildResponse) {
    var self = this;

    req.withArg1(function arg1Ready(err, arg1) {
        if (err) {
            // TODO: log error
            buildResponse({streamed: false}).sendError('UnexpectedError', util.format(
                'error accumulating arg1: %s: %s',
                err.constructor.name, err.message));
            return;
        }

        var name = String(arg1);
        var handler = self.endpoints[name];
        self.handleEndpointEvent.emit(self, {
            name: name,
            handler: handler
        });
        if (!handler) {
            buildResponse({streamed: false}).sendError('BadRequest', util.format(
                'no such endpoint service=%j endpoint=%j',
                req.serviceName, name));
        } else {
            handler.handleRequest(req, buildResponse);
        }
    });
};

TChannelEndpointHandler.prototype.withArg23 = function withArg23(req, buildResponse, handler) {
    req.withArg23(function gotArg23(err, arg2, arg3) {
        var res = buildResponse({streamed: false});
        if (err) {
            // TODO: log error
            res.sendError('UnexpectedError', util.format(
                'error accumulating arg2/arg3: %s: %s',
                err.constructor.name, err.message));
        } else {
            handler.handleRequest(req, res, arg2, arg3);
        }
    });
};

module.exports = TChannelEndpointHandler;
<commit_msg>TChannelEndpointHandler: drop support for streaming arg1<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var EventEmitter = require('./lib/event_emitter');
var inherits = require('util').inherits;
var util = require('util');
var errors = require('./errors');
var coerceRequestHandler = require('./request-handler');

function TChannelEndpointHandler(serviceName) {
    if (!(this instanceof TChannelEndpointHandler)) {
        return new TChannelEndpointHandler(serviceName);
    }
    var self = this;
    EventEmitter.call(self);
    self.handleEndpointEvent = self.defineEvent('handleEndpoint');

    self.serviceName = serviceName;
    self.endpoints = Object.create(null);
}
inherits(TChannelEndpointHandler, EventEmitter);

TChannelEndpointHandler.prototype.type = 'tchannel.endpoint-handler';

TChannelEndpointHandler.prototype.register = function register(name, options, handler) {
    var self = this;
    if (typeof options === 'function') {
        handler = options;
        options = {};
    }
    if (typeof handler !== 'function') {
        throw errors.InvalidHandlerError();
    }
    if (options.streamed) handler.canStream = true;
    self.endpoints[name] = coerceRequestHandler(handler, self, options);
    return handler;
};

TChannelEndpointHandler.prototype.handleRequest = function handleRequest(req, buildResponse) {
    var self = this;

    var handler = self.endpoints[req.endpoint];
    self.handleEndpointEvent.emit(self, {
        name: req.endpoint,
        handler: handler
    });
    if (!handler) {
        buildResponse({streamed: false}).sendError('BadRequest', util.format(
            'no such endpoint service=%j endpoint=%j',
            req.serviceName, req.endpoint));
    } else {
        handler.handleRequest(req, buildResponse);
    }
};

TChannelEndpointHandler.prototype.withArg23 = function withArg23(req, buildResponse, handler) {
    req.withArg23(function gotArg23(err, arg2, arg3) {
        var res = buildResponse({streamed: false});
        if (err) {
            // TODO: log error
            res.sendError('UnexpectedError', util.format(
                'error accumulating arg2/arg3: %s: %s',
                err.constructor.name, err.message));
        } else {
            handler.handleRequest(req, res, arg2, arg3);
        }
    });
};

module.exports = TChannelEndpointHandler;
<|endoftext|>


<commit_before>/*global describe, it, setTimeout, Symbol*/
var arrayChanges = require('../lib/arrayChanges');
var expect = require('unexpected');

function toArguments() {
    return arguments;
}
function promiseArrayChanges(actual, expected, equal, similar, includeNonNumericalProperties) {
    var isCalled = 0;
    return expect.promise(function (resolve, reject) {
       arrayChanges(actual, expected, equal, similar, includeNonNumericalProperties, function (itemDiff) {
           isCalled += 1;
           var stack = '';

           // Grab the stack now, in case the callback is called more than once
           try {
               throw new Error();
           } catch (e) {
               stack = e.stack;
           }

           setTimeout(function () {

               if (isCalled !== 1) {
                   try {
                       throw new Error();
                   } catch (e) {
                       return reject('callback called more than once' + stack);
                   }
               }
               resolve(itemDiff);
           });

       });
    });
}

describe('array-changes-async', function () {
    it('returns an empty change-list when the two arrays are both empty', function () {
        return expect(promiseArrayChanges([], [], function (a, b, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to equal', []);
    });

    it('returns a change-list with no changes if the arrays are the same', function () {
        return expect(promiseArrayChanges([0, 1, 2, 3], [0, 1, 2, 3], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to satisfy', [
            { type: 'equal', value: 0, expected: 0 },
            { type: 'equal', value: 1, expected: 1 },
            { type: 'equal', value: 2, expected: 2 },
            { type: 'equal', value: 3, expected: 3, last: true }
        ]);
    });

    it('should indicate item removals', function () {
        return expect(promiseArrayChanges([0, 1, 2, 3], [0, 1, 3], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to satisfy', [
            { type: 'equal', value: 0, expected: 0 },
            { type: 'equal', value: 1, expected: 1 },
            { type: 'remove', value: 2 },
            { type: 'equal', value: 3, expected: 3, last: true }
        ]);
    });

    it('should indicate item removals at the end', function () {
        return expect(promiseArrayChanges([0], [], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to equal', [
            { type: 'remove', value: 0, last: true }
        ]);
    });

    it('should indicate missing items', function () {
        return expect(promiseArrayChanges([0, 1, 3], [0, 1, 2, 3], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to satisfy', [
            { type: 'equal', value: 0, expected: 0 },
            { type: 'equal', value: 1, expected: 1 },
            { type: 'insert', value: 2 },
            { type: 'equal', value: 3, last: true, expected: 3 }
        ]);
    });

    it('should indicate a missing item at the end', function () {
        return expect(promiseArrayChanges([], [0], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to satisfy', [
            { type: 'insert', value: 0, last: true }
        ]);
    });

    it('should treat moved items as removed and inserted', function () {
        return expect(promiseArrayChanges([1, 2, 3, 0], [0, 1, 2, 3], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to satisfy', [
            { type: 'insert', value: 0, last: false },
            { type: 'equal', value: 1, expected: 1 },
            { type: 'equal', value: 2, expected: 2 },
            { type: 'equal', value: 3, expected: 3 },
            { type: 'remove', value: 0, last: true }
        ]);
    });

    it('shows items that are not equal but should be compared against each other as similar', function () {
        return expect(promiseArrayChanges([0, 1, 2, 3], [0, 2, 1, 3], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }, function (a, b, aIndex, bIndex, callback) {
            callback(true);
        }), 'when fulfilled', 'to satisfy', [
                { type: 'equal', value: 0, expected: 0 },
                { type: 'similar', value: 1, expected: 2 },
                { type: 'similar', value: 2, expected: 1 },
                { type: 'equal', value: 3, expected: 3, last: true }
            ]
        );
    });

    it('allows you to indicate which items should be considered similar', function () {
        return expect(promiseArrayChanges([
            { type: 'dog', name: 'Fido' },
            { type: 'dog', name: 'Teddy' },
            { type: 'person', name: 'Sune' },
            { type: 'dog', name: 'Charlie' },
            { type: 'dog', name: 'Sam' }
        ], [
            { type: 'dog', name: 'Fido' },
            { type: 'dog', name: 'Teddy' },
            { type: 'dog', name: 'Murphy' },
            { type: 'person', name: 'Andreas' },
            { type: 'dog', name: 'Charlie' },
            { type: 'dog', name: 'Sam' }
        ], function (a, b, aIndex, bIndex, callback) {
            return callback(a.type === b.type && a.name === b.name);
        }, function (a, b, aIndex, bIndex, callback) {
            return callback(a.type === b.type);
        }), 'when fulfilled', 'to satisfy', [
            {
                type: 'equal',
                value: { type: 'dog', name: 'Fido' },
                expected: { type: 'dog', name: 'Fido' }
            },
            {
                type: 'equal',
                value: { type: 'dog', name: 'Teddy' },
                expected: { type: 'dog', name: 'Teddy' }
            },
            {
                type: 'insert',
                value: { type: 'dog', name: 'Murphy' }
            },
            {
                type: 'similar',
                value: { type: 'person', name: 'Sune' },
                expected: { type: 'person', name: 'Andreas' }
            },
            {
                type: 'equal',
                value: { type: 'dog', name: 'Charlie' },
                expected: { type: 'dog', name: 'Charlie' }
            },
            {
                type: 'equal',
                value: { type: 'dog', name: 'Sam' },
                last: true,
                expected: { type: 'dog', name: 'Sam' }
            }
        ]);
    });

    it('supports diffing array-like objects', function () {
        return expect(promiseArrayChanges(toArguments(1, 2, 5), toArguments(3, 4), function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to satisfy', [
            { type: 'insert', value: toArguments( 3, 4 ) },
            { type: 'remove', value: 1 },
            { type: 'similar', value: 2, expected: 4 },
            { type: 'similar', value: 5, last: true, expected: undefined }
        ]);
    });

    it('supports diffing spotting non-similar objects', function () {

        return expect(promiseArrayChanges([ { name: 'steve' }, { name: 'monica' }, { name: 'sam' } ],
            [ { name: 'andreas' }, { name: 'sune' }, { name: 'peter' } ], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }, function (a, b, aIndex, bIndex, callback) {
            return callback(false); // nothing is similar
        }), 'when fulfilled', 'to satisfy', [
            { type: 'similar', value: { name: 'steve' } },  // This feels wrong
            { type: 'similar', value: { name: 'monica' } },
            { type: 'similar', value: { name: 'sam' } }
        ]);
    });

    it('supports diffing non-matching types', function () {
        // This test is specifically targeted to exercise the "similar, but not object or string"
        // part of the algorithm. I believe the type checks should come out of array-changes, and
        // go back to unexpected (where they came from), as they don't make sense here
        // What is equal, and what is similar should be a pure decision of the functions passed in,
        // and nothing to do with the types of the values.
        // UPDATE: The "similar, but not object or string" checks have been removed, this test still works :)
        return expect(promiseArrayChanges([ true, false, true ],
            [ { name: 'steve' }, { name: 'monica' }, { name: 'sam' } ], function (a, b, aIndex, bIndex, callback) {
                return callback(a === b);
            }, function (a, b, aIndex, bIndex, callback) {
                return callback(true); // everything is similar
            }), 'when fulfilled', 'to satisfy', [
            { type: 'similar', value: true },
            { type: 'similar', value: false },
            { type: 'similar', value: true }
        ]);

    });

    it('matches the example in the readme', function () {

        return expect(promiseArrayChanges([ 1, 2, 4 ], [ 1, 2, 3, 4 ], function (a, b, aIndex, bIndex, callback) {
            callback(a === b)
        }, null), 'when fulfilled', 'to satisfy', [
            { type: 'equal', value: 1 },
            { type: 'equal', value: 2 },
            { type: 'insert', value: 3 },
            { type: 'equal', value: 4 }
        ]);
    });

    it('marks items as similar only when the similar function reports they are similar', function () {

        return expect(promiseArrayChanges(
            [ 1, 2.4, /* missing*/  4 ],
            [ 1, 2,   3,            4.7 ],
            function (a, b, aIndex, bIndex, callback) {
            callback(a === b)
        }, function (a, b, aIndex, bIndex, callback) {
                if (Math.floor(a) === Math.floor(b)) {
                    return callback(true);
                }
                return callback(false);
            }), 'when fulfilled', 'to satisfy', [
            { type: 'equal', value: 1 },
            { type: 'similar', value: 2.4, expected: 2 },
            { type: 'insert', value: 3 },
            { type: 'similar', value: 4, expected: 4.7 }
        ]);
    });

    it('should diff arrays that have non-numerical property names', function () {
        var a = [1, 2, 3];
        a.foo = 123;
        a.bar = 456;
        a.quux = {};

        var b = [1, 2, 3];
        b.bar = 456;
        b.baz = 789;
        b.quux = false;
        return expect(promiseArrayChanges(a, b, function (a, b, aIndex, bIndex, callback) {
            callback(a === b);
        }, function (a, b, aIndex, bIndex, callback) {
            callback(a === b);
        }, true), 'when fulfilled', 'to equal', [
            { type: 'equal', value: 1, expected: 1, actualIndex: 0, expectedIndex: 0 },
            { type: 'equal', value: 2, expected: 2, actualIndex: 1, expectedIndex: 1 },
            { type: 'equal', value: 3, expected: 3, actualIndex: 2, expectedIndex: 2 },
            { type: 'remove', value: 123, actualIndex: 'foo' },
            { type: 'equal', value: 456, expected: 456, actualIndex: 'bar', expectedIndex: 'bar' },
            { type: 'similar', value: {}, expected: false, actualIndex: 'quux', expectedIndex: 'quux' },
            { type: 'insert', value: 789, expectedIndex: 'baz', last: true }
        ]);
    });

    it('should support an array of specific non-numerical keys to diff', function () {
        var a = [1];
        a.foo = 123;
        a.bar = 789;

        var b = [1];
        a.foo = 456;
        a.bar = false;
        return expect(promiseArrayChanges(a, b, function (a, b, aIndex, bIndex, callback) {
            callback(a === b);
        }, function (a, b, aIndex, bIndex, callback) {
            callback(a === b);
        }, [ 'foo' ]), 'when fulfilled', 'to equal', [
            { type: 'equal', actualIndex: 0, expectedIndex: 0, value: 1, expected: 1 },
            { type: 'remove', actualIndex: 'foo', value: 456, last: true }
        ]);
    });

    if (typeof Symbol !== 'undefined') {
        it('should diff arrays that have Symbol property names', function () {
            var aSymbol = Symbol('a');
            var bSymbol = Symbol('b');
            var a = [1, 2];
            a[aSymbol] = 123;

            var b = [1, 2];
            b[bSymbol] = 456;
            return expect(promiseArrayChanges(a, b, function (a, b, aIndex, bIndex, callback) {
                callback(a === b);
            }, function (a, b, aIndex, bIndex, callback) {
                callback(a === b);
            }, true), 'when fulfilled', 'to equal', [
                { type: 'equal', value: 1, expected: 1, actualIndex: 0, expectedIndex: 0 },
                { type: 'equal', value: 2, expected: 2, actualIndex: 1, expectedIndex: 1 },
                { type: 'remove', value: 123, actualIndex: aSymbol },
                { type: 'insert', value: 456, expectedIndex: bSymbol, last: true }
            ]);
        });
    }
});
<commit_msg>Add test for moves<commit_after>/*global describe, it, setTimeout, Symbol*/
var arrayChanges = require('../lib/arrayChanges');
var expect = require('unexpected');

function toArguments() {
    return arguments;
}
function promiseArrayChanges(actual, expected, equal, similar, includeNonNumericalProperties) {
    var isCalled = 0;
    return expect.promise(function (resolve, reject) {
       arrayChanges(actual, expected, equal, similar, includeNonNumericalProperties, function (itemDiff) {
           isCalled += 1;
           var stack = '';

           // Grab the stack now, in case the callback is called more than once
           try {
               throw new Error();
           } catch (e) {
               stack = e.stack;
           }

           setTimeout(function () {

               if (isCalled !== 1) {
                   return reject('callback called more than once' + stack);
               }
               resolve(itemDiff);
           });

       });
    });
}

describe('array-changes-async', function () {
    it('returns an empty change-list when the two arrays are both empty', function () {
        return expect(promiseArrayChanges([], [], function (a, b, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to equal', []);
    });

    it('returns a change-list with no changes if the arrays are the same', function () {
        return expect(promiseArrayChanges([0, 1, 2, 3], [0, 1, 2, 3], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to satisfy', [
            { type: 'equal', value: 0, expected: 0 },
            { type: 'equal', value: 1, expected: 1 },
            { type: 'equal', value: 2, expected: 2 },
            { type: 'equal', value: 3, expected: 3, last: true }
        ]);
    });

    it('should indicate item removals', function () {
        return expect(promiseArrayChanges([0, 1, 2, 3], [0, 1, 3], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to satisfy', [
            { type: 'equal', value: 0, expected: 0 },
            { type: 'equal', value: 1, expected: 1 },
            { type: 'remove', value: 2 },
            { type: 'equal', value: 3, expected: 3, last: true }
        ]);
    });

    it('should indicate item removals at the end', function () {
        return expect(promiseArrayChanges([0], [], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to equal', [
            { type: 'remove', value: 0, last: true }
        ]);
    });

    it('should indicate missing items', function () {
        return expect(promiseArrayChanges([0, 1, 3], [0, 1, 2, 3], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to satisfy', [
            { type: 'equal', value: 0, expected: 0 },
            { type: 'equal', value: 1, expected: 1 },
            { type: 'insert', value: 2 },
            { type: 'equal', value: 3, last: true, expected: 3 }
        ]);
    });

    it('should indicate a missing item at the end', function () {
        return expect(promiseArrayChanges([], [0], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to satisfy', [
            { type: 'insert', value: 0, last: true }
        ]);
    });

    it('should treat moved items as removed and inserted', function () {
        return expect(promiseArrayChanges([1, 2, 3, 0], [0, 1, 2, 3], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to satisfy', [
            { type: 'insert', value: 0, last: false },
            { type: 'equal', value: 1, expected: 1 },
            { type: 'equal', value: 2, expected: 2 },
            { type: 'equal', value: 3, expected: 3 },
            { type: 'remove', value: 0, last: true }
        ]);
    });

    it('shows items that are not equal but should be compared against each other as similar', function () {
        return expect(promiseArrayChanges([0, 1, 2, 3], [0, 2, 1, 3], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }, function (a, b, aIndex, bIndex, callback) {
            callback(true);
        }), 'when fulfilled', 'to satisfy', [
                { type: 'equal', value: 0, expected: 0 },
                { type: 'similar', value: 1, expected: 2 },
                { type: 'similar', value: 2, expected: 1 },
                { type: 'equal', value: 3, expected: 3, last: true }
            ]
        );
    });

    it('allows you to indicate which items should be considered similar', function () {
        return expect(promiseArrayChanges([
            { type: 'dog', name: 'Fido' },
            { type: 'dog', name: 'Teddy' },
            { type: 'person', name: 'Sune' },
            { type: 'dog', name: 'Charlie' },
            { type: 'dog', name: 'Sam' }
        ], [
            { type: 'dog', name: 'Fido' },
            { type: 'dog', name: 'Teddy' },
            { type: 'dog', name: 'Murphy' },
            { type: 'person', name: 'Andreas' },
            { type: 'dog', name: 'Charlie' },
            { type: 'dog', name: 'Sam' }
        ], function (a, b, aIndex, bIndex, callback) {
            return callback(a.type === b.type && a.name === b.name);
        }, function (a, b, aIndex, bIndex, callback) {
            return callback(a.type === b.type);
        }), 'when fulfilled', 'to satisfy', [
            {
                type: 'equal',
                value: { type: 'dog', name: 'Fido' },
                expected: { type: 'dog', name: 'Fido' }
            },
            {
                type: 'equal',
                value: { type: 'dog', name: 'Teddy' },
                expected: { type: 'dog', name: 'Teddy' }
            },
            {
                type: 'insert',
                value: { type: 'dog', name: 'Murphy' }
            },
            {
                type: 'similar',
                value: { type: 'person', name: 'Sune' },
                expected: { type: 'person', name: 'Andreas' }
            },
            {
                type: 'equal',
                value: { type: 'dog', name: 'Charlie' },
                expected: { type: 'dog', name: 'Charlie' }
            },
            {
                type: 'equal',
                value: { type: 'dog', name: 'Sam' },
                last: true,
                expected: { type: 'dog', name: 'Sam' }
            }
        ]);
    });

    it('supports diffing array-like objects', function () {
        return expect(promiseArrayChanges(toArguments(1, 2, 5), toArguments(3, 4), function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }), 'when fulfilled', 'to satisfy', [
            { type: 'insert', value: toArguments( 3, 4 ) },
            { type: 'remove', value: 1 },
            { type: 'similar', value: 2, expected: 4 },
            { type: 'similar', value: 5, last: true, expected: undefined }
        ]);
    });

    it('supports diffing spotting non-similar objects', function () {

        return expect(promiseArrayChanges([ { name: 'steve' }, { name: 'monica' }, { name: 'sam' } ],
            [ { name: 'andreas' }, { name: 'sune' }, { name: 'peter' } ], function (a, b, aIndex, bIndex, callback) {
            return callback(a === b);
        }, function (a, b, aIndex, bIndex, callback) {
            return callback(false); // nothing is similar
        }), 'when fulfilled', 'to satisfy', [
            { type: 'similar', value: { name: 'steve' } },  // This feels wrong
            { type: 'similar', value: { name: 'monica' } },
            { type: 'similar', value: { name: 'sam' } }
        ]);
    });

    it('supports diffing non-matching types', function () {
        // This test is specifically targeted to exercise the "similar, but not object or string"
        // part of the algorithm. I believe the type checks should come out of array-changes, and
        // go back to unexpected (where they came from), as they don't make sense here
        // What is equal, and what is similar should be a pure decision of the functions passed in,
        // and nothing to do with the types of the values.
        // UPDATE: The "similar, but not object or string" checks have been removed, this test still works :)
        return expect(promiseArrayChanges([ true, false, true ],
            [ { name: 'steve' }, { name: 'monica' }, { name: 'sam' } ], function (a, b, aIndex, bIndex, callback) {
                return callback(a === b);
            }, function (a, b, aIndex, bIndex, callback) {
                return callback(true); // everything is similar
            }), 'when fulfilled', 'to satisfy', [
            { type: 'similar', value: true },
            { type: 'similar', value: false },
            { type: 'similar', value: true }
        ]);

    });

    it('matches the example in the readme', function () {

        return expect(promiseArrayChanges([ 1, 2, 4 ], [ 1, 2, 3, 4 ], function (a, b, aIndex, bIndex, callback) {
            callback(a === b)
        }, null), 'when fulfilled', 'to satisfy', [
            { type: 'equal', value: 1 },
            { type: 'equal', value: 2 },
            { type: 'insert', value: 3 },
            { type: 'equal', value: 4 }
        ]);
    });

    it('marks items as similar only when the similar function reports they are similar', function () {

        return expect(promiseArrayChanges(
            [ 1, 2.4, /* missing*/  4 ],
            [ 1, 2,   3,            4.7 ],
            function (a, b, aIndex, bIndex, callback) {
            callback(a === b)
        }, function (a, b, aIndex, bIndex, callback) {
                if (Math.floor(a) === Math.floor(b)) {
                    return callback(true);
                }
                return callback(false);
            }), 'when fulfilled', 'to satisfy', [
            { type: 'equal', value: 1 },
            { type: 'similar', value: 2.4, expected: 2 },
            { type: 'insert', value: 3 },
            { type: 'similar', value: 4, expected: 4.7 }
        ]);
    });

    it('does not provide the value from the expected if the value moves', function () {

        // If the `similar` callback returns false when `equal` returns true, the algorithm
        // gets the wrong answer, and includes the expected value as `value` in the result
        // This isn't a problem with the algorithm, as an equal value must be similar,
        // but this test just checks that for a move operation, we don't get values from the
        // "wrong" side
        return expect(promiseArrayChanges(
            [ 4, 2, 3, 1  ], [ 100, 200, 300, 400 ], function (a, b, aIndex, bIndex, callback){
                return callback(a * 100 === b);
            }, function (a, b, aIndex, bIndex, callback) { return callback(a * 100 === b); }),
            'when fulfilled', 'to satisfy', [
                { type: 'similar', value: 4, expected: 100 },
                { type: 'equal', value: 2, expected: 200 },
                { type: 'equal', value: 3, expected: 300 },
                { type: 'similar', value: 1, expected: 400 }
            ]);
    });

    it('should diff arrays that have non-numerical property names', function () {
        var a = [1, 2, 3];
        a.foo = 123;
        a.bar = 456;
        a.quux = {};

        var b = [1, 2, 3];
        b.bar = 456;
        b.baz = 789;
        b.quux = false;
        return expect(promiseArrayChanges(a, b, function (a, b, aIndex, bIndex, callback) {
            callback(a === b);
        }, function (a, b, aIndex, bIndex, callback) {
            callback(a === b);
        }, true), 'when fulfilled', 'to equal', [
            { type: 'equal', value: 1, expected: 1, actualIndex: 0, expectedIndex: 0 },
            { type: 'equal', value: 2, expected: 2, actualIndex: 1, expectedIndex: 1 },
            { type: 'equal', value: 3, expected: 3, actualIndex: 2, expectedIndex: 2 },
            { type: 'remove', value: 123, actualIndex: 'foo' },
            { type: 'equal', value: 456, expected: 456, actualIndex: 'bar', expectedIndex: 'bar' },
            { type: 'similar', value: {}, expected: false, actualIndex: 'quux', expectedIndex: 'quux' },
            { type: 'insert', value: 789, expectedIndex: 'baz', last: true }
        ]);
    });

    it('should support an array of specific non-numerical keys to diff', function () {
        var a = [1];
        a.foo = 123;
        a.bar = 789;

        var b = [1];
        a.foo = 456;
        a.bar = false;
        return expect(promiseArrayChanges(a, b, function (a, b, aIndex, bIndex, callback) {
            callback(a === b);
        }, function (a, b, aIndex, bIndex, callback) {
            callback(a === b);
        }, [ 'foo' ]), 'when fulfilled', 'to equal', [
            { type: 'equal', actualIndex: 0, expectedIndex: 0, value: 1, expected: 1 },
            { type: 'remove', actualIndex: 'foo', value: 456, last: true }
        ]);
    });

    if (typeof Symbol !== 'undefined') {
        it('should diff arrays that have Symbol property names', function () {
            var aSymbol = Symbol('a');
            var bSymbol = Symbol('b');
            var a = [1, 2];
            a[aSymbol] = 123;

            var b = [1, 2];
            b[bSymbol] = 456;
            return expect(promiseArrayChanges(a, b, function (a, b, aIndex, bIndex, callback) {
                callback(a === b);
            }, function (a, b, aIndex, bIndex, callback) {
                callback(a === b);
            }, true), 'when fulfilled', 'to equal', [
                { type: 'equal', value: 1, expected: 1, actualIndex: 0, expectedIndex: 0 },
                { type: 'equal', value: 2, expected: 2, actualIndex: 1, expectedIndex: 1 },
                { type: 'remove', value: 123, actualIndex: aSymbol },
                { type: 'insert', value: 456, expectedIndex: bSymbol, last: true }
            ]);
        });
    }
});

<|endoftext|>


<commit_before>'use strict';

var Backbone = require('../vendor/index').Backbone;
var $ = require('../vendor/index').$;
var Model = require('./learning-resource.model');
var View = require('./learning-resource.view');

module.exports = Backbone.Controller.extend({

  routes: {
    'learning-resource/:': 'list',
    'learning-resource/:id': 'showLearningResource'
  },

  initialize: function(){
    this.options.container = this.options.container || 'body';
    this.model = new Model();
    this.view = new View({model: this.model});
  },

  // list: function(){
  //   this.resourceList = new Model();
  //   this.resourceListView = new View();
  //   this.resourceList.fetch();
  //   $('body').html(this.resourceListView.render.el);
  // },

  showLearningResource: function(learningResourceId, cb){
    this.fetchModel(learningResourceId, function(err, model){
      var view;

      if (err){
        view = this.renderError();
      } else {
        this.view.remove();
        this.view = new View({model: model});
        view = this.renderView();
      }

      if (cb){
        cb(err, view);
      }

    }.bind(this));
  },

  fetchModel: function(learningResourceId, cb){
    this.model.set({id: learningResourceId});

    this.model.fetch({
      success: function(model, response, options){
        //console.log(model);
        cb(null, model);
      },
      error: function(model, response, options){
        //console.error(response);
        cb(response, model);
      }
    });
  },

  renderToContainer: function(view){
    return $(this.options.container).html(view);
  },

  renderView: function(){
    this.renderToContainer(this.view.render().$el);
    return this.view;
  },

  renderError: function(){
    return this.renderToContainer(
      '
There was a problem rendering this learning resource.
');
  }

});
<commit_msg>- Removes default view initialization and adds a view existence check on   showLearningResource - Move default new model initialization to fetchModel and check existence   of learningResourceId to set model<commit_after>'use strict';

var Backbone = require('../vendor/index').Backbone;
var $ = require('../vendor/index').$;
var Model = require('./learning-resource.model');
var View = require('./learning-resource.view');

module.exports = Backbone.Controller.extend({

  routes: {
    'learning-resource/:': 'list',
    'learning-resource/:id': 'showLearningResource'
  },

  initialize: function(){
    this.options.container = this.options.container || 'body';
  },

  // list: function(){
  //   this.resourceList = new Model();
  //   this.resourceListView = new View();
  //   this.resourceList.fetch();
  //   $('body').html(this.resourceListView.render.el);
  // },

  showLearningResource: function(learningResourceId, cb){
    this.fetchModel(learningResourceId, function(err, model){
      var view;

      if (err){
        view = this.renderError();
      } else {
        if (this.view) this.view.remove();
        this.view = new View({model: model});
        view = this.renderView();
      }

      if (cb){
        cb(err, view);
      }

    }.bind(this));
  },

  fetchModel: function(learningResourceId, cb){
    this.model = new Model();
    if (learningResourceId) this.model.set({id: learningResourceId});

    this.model.fetch({
      success: function(model, response, options){
        //console.log(model);
        cb(null, model);
      },
      error: function(model, response, options){
        //console.error(response);
        cb(response, model);
      }
    });
  },

  renderToContainer: function(view){
    return $(this.options.container).html(view);
  },

  renderView: function(){
    this.renderToContainer(this.view.render().$el);
    return this.view;
  },

  renderError: function(){
    return this.renderToContainer(
      '
There was a problem rendering this learning resource.
');
  }

});
<|endoftext|>


<commit_before>import parse from 'url-parse'
import {formatForXHR} from './url'

export function isValidResponse (xhr) {
  return isValidContent(xhr) && !downloadingFile(xhr)
}

export function parseSJR (body) {
  return (new Function(`'use strict'; return ${body}` )())
}

export function isValidContent (rsp) {
  const contentType = rsp.headers.get('content-type')
  const jsContent = /^(?:text\/javascript|application\/x-javascript|application\/javascript)(?:;|$)/

  return !!(contentType !== undefined && contentType.match(jsContent))
}

function downloadingFile (xhr) {
  const disposition = xhr.headers.get('content-disposition')
  return disposition !== undefined && disposition !== null && disposition.match(/^attachment/)
}

export function validateResponse (args) {
  const {rsp} = args
  if(isValidResponse(rsp)) {
    return args
  } else {
    const error = new Error('Invalid Breezy Response')
    error.response = rsp
    throw error
  }
}

export function handleServerErrors (args){
  const {rsp} = args
  if (!rsp.ok) {
    const error = new Error(rsp.statusText)
    error.response = rsp
    throw error
  }
  return args
}

export function argsForFetch (getState, pathQuery, {method='GET', headers = {}, body}) {
  const currentState = getState().breezy || {}

  const jsAccept = 'text/javascript, application/x-javascript, application/javascript'
  headers = {
    ...headers,
    'accept': jsAccept,
    'x-requested-with': 'XMLHttpRequest',
    'x-breezy-request': true
  }

  if (currentState.currentUrl) {
    headers['x-xhr-referer'] = currentState.currentUrl
  }

  if (currentState.csrfToken) {
    headers['x-csrf-token'] = currentState.csrfToken
  }

  const href = new parse(pathQuery, currentState.baseUrl || '', false).href
  const credentials = 'same-origin'

  if (!(method == 'GET' || method == 'HEAD')) {
    headers['x-http-method-override'] = method
    method = 'POST'
  }

  const options = {method, headers, body, credentials}

  if (method == 'GET' || method == 'HEAD') {
    delete options.body
  }

  return [formatForXHR(href), options]
}

export function extractText (rsp) {
  return rsp.text().then((txt) => {
    return {rsp, txt}
  })
}

export function extractSJR ({rsp, txt}) {
  const page = parseSJR(txt)
  if (page) {
    return Promise.resolve({rsp, page})
  } else {
    const error = new Error('Could not parse Server Generated Javascript Response for Breezy')
    error.response = rsp

    throw error
  }
}

export function parseResponse (prm) {
  return Promise.resolve(prm)
    .then(extractText)
    .then(handleServerErrors)
    .then(validateResponse)
    .then(extractSJR)
}
<commit_msg>Non-GET requests are in json so rails can parse<commit_after>import parse from 'url-parse'
import {formatForXHR} from './url'

export function isValidResponse (xhr) {
  return isValidContent(xhr) && !downloadingFile(xhr)
}

export function parseSJR (body) {
  return (new Function(`'use strict'; return ${body}` )())
}

export function isValidContent (rsp) {
  const contentType = rsp.headers.get('content-type')
  const jsContent = /^(?:text\/javascript|application\/x-javascript|application\/javascript)(?:;|$)/

  return !!(contentType !== undefined && contentType.match(jsContent))
}

function downloadingFile (xhr) {
  const disposition = xhr.headers.get('content-disposition')
  return disposition !== undefined && disposition !== null && disposition.match(/^attachment/)
}

export function validateResponse (args) {
  const {rsp} = args
  if(isValidResponse(rsp)) {
    return args
  } else {
    const error = new Error('Invalid Breezy Response')
    error.response = rsp
    throw error
  }
}

export function handleServerErrors (args){
  const {rsp} = args
  if (!rsp.ok) {
    const error = new Error(rsp.statusText)
    error.response = rsp
    throw error
  }
  return args
}

export function argsForFetch (getState, pathQuery, {method='GET', headers = {}, body}) {
  const currentState = getState().breezy || {}

  const jsAccept = 'text/javascript, application/x-javascript, application/javascript'
  headers = {
    ...headers,
    'accept': jsAccept,
    'x-requested-with': 'XMLHttpRequest',
    'x-breezy-request': true,
  }

  if(method != 'GET') {
    headers['content-type'] = 'application/json'
  }

  if (currentState.currentUrl) {
    headers['x-xhr-referer'] = currentState.currentUrl
  }

  if (currentState.csrfToken) {
    headers['x-csrf-token'] = currentState.csrfToken
  }

  const href = new parse(pathQuery, currentState.baseUrl || '', false).href
  const credentials = 'same-origin'

  if (!(method == 'GET' || method == 'HEAD')) {
    headers['x-http-method-override'] = method
    method = 'POST'
  }

  const options = {method, headers, body, credentials}

  if (method == 'GET' || method == 'HEAD') {
    delete options.body
  }

  return [formatForXHR(href), options]
}

export function extractText (rsp) {
  return rsp.text().then((txt) => {
    return {rsp, txt}
  })
}

export function extractSJR ({rsp, txt}) {
  const page = parseSJR(txt)
  if (page) {
    return Promise.resolve({rsp, page})
  } else {
    const error = new Error('Could not parse Server Generated Javascript Response for Breezy')
    error.response = rsp

    throw error
  }
}

export function parseResponse (prm) {
  return Promise.resolve(prm)
    .then(extractText)
    .then(handleServerErrors)
    .then(validateResponse)
    .then(extractSJR)
}
<|endoftext|>