<commit_before>'use strict';

require.config({

  baseUrl: '/assets/application',

  paths: {
    jquery: '/app/vendor/jquery/dist/jquery',
    underscore: '/app/vendor/underscore/underscore',
    underscoreString: '/app/vendor/underscore.string/lib/underscore.string',
    amplify: '/app/vendor/amplify/lib/amplify',
    backbone: '/app/vendor/backbone/backbone',
    Class: '/app/vendor/class.js/Class',
    select2: '/app/vendor/select2/select2',
    handlebars: '/app/vendor/handlebars/handlebars',
    highcharts: '/app/vendor/highcharts-release/highcharts',
    spin: '/app/vendor/spinjs/spin',
    moment: '/app/vendor/moment/moment',
    momentRange: '/app/vendor/moment-range/lib/moment-range.bare',
    markerCluster: '/app/vendor/leaflet.markercluster/dist/leaflet.markercluster',
    text: '/app/vendor/requirejs-text/text',
    quicksilver: '/app/lib/liveupdate/quicksilver',
    liveupdate: '/app/lib/liveupdate/jquery.liveupdate',
    jqueryui: '/app/lib/jquery-ui/js/jquery-ui-1.10.4.custom',
    chachiSlider: '/app/vendor/chachi-slider/jquery.chachi-slider'
  },

  shim: {
    jquery: {
      exports: '$'
    },
    underscore: {
      exports: '_'
    },
    underscoreString: {
      deps: ['underscore'],
      exports: '_.str'
    },
    backbone: {
      deps: ['jquery', 'underscore'],
      exports: 'Backbone'
    },
    liveupdate: {
      deps: ['jquery', 'quicksilver'],
      exports: '$'
    },
    jqueryui: {
      deps: ['jquery'],
      exports: '$'
    },
    select2: {
      deps: ['jquery'],
      exports: '$'
    },
    chachiSlider: {
      deps: ['jquery'],
      exports: '$'
    },
    Class: {
      exports: 'Class'
    },
    amplify: {
      exports: 'amplify'
    }
  }

});

require([
  'jquery',
  'router',
  'chachiSlider'
], function($, Router) {
  // Extensions
  Number.prototype.toCommas = function() {
    return this.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  };

  $.fn.noHandleChildren = function() {

    var SearchMenu = function(el) {
      var $el = $(el),
        menuChildren = $el.find('.submenu li').length;

      if (menuChildren === 0) {
        $el.addClass('no-child');
      }
    };

    return this.each(function(index, el) {
      new SearchMenu(el);
    });
  };

  function sectionTitle() {
    var $title = $('.mod-content-article').find('h1');

    if ($title.text().length > 50) {
      $title.css('font-size', '36px');
    }
  }

  function addClassToBody() {
    var newClass, position;

    position = window.location.pathname.split('/').length - 1;
    newClass = window.location.pathname.split('/')[position];

    $('body').addClass('linos-' + newClass);
  }

  function goTo(e) {
    $('body, html').animate({
      scrollTop: $('.main-content').offset().top - 49
    }, 500);
    e.preventDefault();
  }

  $('.btn-go-to-projects').on('click', goTo);

  sectionTitle();
  addClassToBody();
  $('.menu-item').noHandleChildren();

  var $projectBudget = $('#projectBudgetValue');

  if ($projectBudget.text().length > 8) {
    $projectBudget.css({
      'font-size': '35px'
    });
  }

  $('.mod-logos-slider').chachiSlider({
    navigation: false,
    pauseTime: 7000
  });

  // $('#feedbackBtn').on('click', function(e) {
  //   e.preventDefault();
  //   $('#feedbackOverlay').show();
  // });

  new Router();

});
<commit_msg>uppercase Class<commit_after>'use strict';

require.config({

  baseUrl: '/assets/application',

  paths: {
    jquery: '/app/vendor/jquery/dist/jquery',
    underscore: '/app/vendor/underscore/underscore',
    underscoreString: '/app/vendor/underscore.string/lib/underscore.string',
    amplify: '/app/vendor/amplify/lib/amplify',
    backbone: '/app/vendor/backbone/backbone',
    Class: '/app/vendor/Class.js/Class',
    select2: '/app/vendor/select2/select2',
    handlebars: '/app/vendor/handlebars/handlebars',
    highcharts: '/app/vendor/highcharts-release/highcharts',
    spin: '/app/vendor/spinjs/spin',
    moment: '/app/vendor/moment/moment',
    momentRange: '/app/vendor/moment-range/lib/moment-range.bare',
    markerCluster: '/app/vendor/leaflet.markercluster/dist/leaflet.markercluster',
    text: '/app/vendor/requirejs-text/text',
    quicksilver: '/app/lib/liveupdate/quicksilver',
    liveupdate: '/app/lib/liveupdate/jquery.liveupdate',
    jqueryui: '/app/lib/jquery-ui/js/jquery-ui-1.10.4.custom',
    chachiSlider: '/app/vendor/chachi-slider/jquery.chachi-slider'
  },

  shim: {
    jquery: {
      exports: '$'
    },
    underscore: {
      exports: '_'
    },
    underscoreString: {
      deps: ['underscore'],
      exports: '_.str'
    },
    backbone: {
      deps: ['jquery', 'underscore'],
      exports: 'Backbone'
    },
    liveupdate: {
      deps: ['jquery', 'quicksilver'],
      exports: '$'
    },
    jqueryui: {
      deps: ['jquery'],
      exports: '$'
    },
    select2: {
      deps: ['jquery'],
      exports: '$'
    },
    chachiSlider: {
      deps: ['jquery'],
      exports: '$'
    },
    Class: {
      exports: 'Class'
    },
    amplify: {
      exports: 'amplify'
    }
  }

});

require([
  'jquery',
  'router',
  'chachiSlider'
], function($, Router) {
  // Extensions
  Number.prototype.toCommas = function() {
    return this.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  };

  $.fn.noHandleChildren = function() {

    var SearchMenu = function(el) {
      var $el = $(el),
        menuChildren = $el.find('.submenu li').length;

      if (menuChildren === 0) {
        $el.addClass('no-child');
      }
    };

    return this.each(function(index, el) {
      new SearchMenu(el);
    });
  };

  function sectionTitle() {
    var $title = $('.mod-content-article').find('h1');

    if ($title.text().length > 50) {
      $title.css('font-size', '36px');
    }
  }

  function addClassToBody() {
    var newClass, position;

    position = window.location.pathname.split('/').length - 1;
    newClass = window.location.pathname.split('/')[position];

    $('body').addClass('linos-' + newClass);
  }

  function goTo(e) {
    $('body, html').animate({
      scrollTop: $('.main-content').offset().top - 49
    }, 500);
    e.preventDefault();
  }

  $('.btn-go-to-projects').on('click', goTo);

  sectionTitle();
  addClassToBody();
  $('.menu-item').noHandleChildren();

  var $projectBudget = $('#projectBudgetValue');

  if ($projectBudget.text().length > 8) {
    $projectBudget.css({
      'font-size': '35px'
    });
  }

  $('.mod-logos-slider').chachiSlider({
    navigation: false,
    pauseTime: 7000
  });

  // $('#feedbackBtn').on('click', function(e) {
  //   e.preventDefault();
  //   $('#feedbackOverlay').show();
  // });

  new Router();

});
<|endoftext|>


<commit_before>$('#panels').change(function(){
	  var request = $.ajax({
	    url: "/workers/filter_workers",
	    type: "GET",
	    data: {panels : document.getElementById("panels").value },
	    dataType: "html"
	    }); //end var request

	    request.done(function( msg ) {
	     $("#panel_results").html( msg );
	    }); //end request.done
 });

$('#panels_right').change(function(){
	  var request = $.ajax({
	    url: "/workers/filter_workers_rightsidebar",
	    type: "GET",
	    data: {panels : document.getElementById("panels_right").value },
	    dataType: "html"
	    }); //end var request

	    request.done(function( msg ) {
	     $("#filter_results").html( msg );
	     copyEmailsToForm();
	    }); //end request.done
 });

var selWorkers = [];

$('input[type=checkbox]').change(function(e){

   if($(this).is(':checked')) {
   		selWorkers.push(this.value);
    } else {
      var removeItem = this.value;
	  selWorkers = jQuery.grep(selWorkers, function(value) {
		  return value != removeItem;
	  });
    }
    console.log(selWorkers);

	var request = $.ajax({
	   url: "/workers/filter_workers_emails",
	   type: "GET",
	   data: {workers : selWorkers },
	   dataType: "html"
	}); //end var request

	request.done(function( msg ) {
		$("#worker_results").html( msg );
		//$("#recipient_email").val( msg );
	}); //end request.done

	//for sidebar view
	var req2 = $.ajax({
	   url: "/workers/right_sidebar_filt",
	   type: "GET",
	   data: {workers : selWorkers },
	   //dataType: "html"
	}); //end var request

	req2.done(function( res ) {
		//$("#worker_results").html( msg );
		res_trim = res.trim();
		$("#recipient_email").val(res_trim);
	}); //end request.done

 });

/*The code below updates the content of the edit modal each time you click on the edit button next to a worker in the panel
(fixes the issue that was causing the same worker information to appear in the edit modal unless you refreshed the page)
found via: http://stackoverflow.com/questions/13104919/twitter-bootstrap-modal-loads-wrong-remote-data
copied from: http://jsfiddle.net/Sherbrow/ThLYb/ */

$('[data-load-remote]').on('click',function(e) {
    e.preventDefault();
    var $this = $(this);
    var remote = $this.data('load-remote');
    if(remote) {
        $($this.data('remote-target')).load(remote);
    }
});

function copyEmailsToForm() {
	console.log("in copy emails");
	emails = document.getElementById("copy-emails-button").getAttribute("data-clipboard-text");
	console.log("emails: " + emails);
 	$("#recipient_email").val(emails);
 }

/*Zero Clipboard All Emails Copy Button */

var client = new ZeroClipboard( document.getElementById("copy-button") );

client.on( "ready", function( readyEvent ) {
  // alert( "ZeroClipboard SWF is ready!" );

  client.on( "aftercopy", function( event ) {
    // `this` === `client`
    // `event.target` === the element that was clicked
    //event.target.style.display = "none";
    alert("Copied emails to clipboard: " + event.data["text/plain"] );
  } );
} );

function insertFoundryLink(sel){
	link = sel.value.split(',');
	foundry_member_url = link[1].substring(2, link[1].length-2);
	console.log("link: " + link[1].substring(2, link[1].length-2));

	$("#foundry_member_url").val(foundry_member_url);
}

function hideRightPanels(){
	$("#hidePanelsLink").html('View Panels');
	$("#right-sidebar-wrapper").css('display', 'none');
	$("#hidePanelsLink").attr('onclick', 'viewRightPanels()');
	$("#email-form-div").removeClass("span5");
	$("#email-form-div").addClass("span9");
}

function viewRightPanels(){
	$("#hidePanelsLink").html('Hide Panels');
	$("#right-sidebar-wrapper").css('display', '');
	$("#hidePanelsLink").attr('onclick', 'hideRightPanels()');
	$("#email-form-div").removeClass("span9");
	$("#email-form-div").addClass("span5");
}
	<commit_msg>fixed the panel resizing issue when you hide and show<commit_after>$('#panels').change(function(){
	  var request = $.ajax({
	    url: "/workers/filter_workers",
	    type: "GET",
	    data: {panels : document.getElementById("panels").value },
	    dataType: "html"
	    }); //end var request

	    request.done(function( msg ) {
	     $("#panel_results").html( msg );
	    }); //end request.done
 });

$('#panels_right').change(function(){
	  var request = $.ajax({
	    url: "/workers/filter_workers_rightsidebar",
	    type: "GET",
	    data: {panels : document.getElementById("panels_right").value },
	    dataType: "html"
	    }); //end var request

	    request.done(function( msg ) {
	     $("#filter_results").html( msg );
	     copyEmailsToForm();
	    }); //end request.done
 });

var selWorkers = [];

$('input[type=checkbox]').change(function(e){

   if($(this).is(':checked')) {
   		selWorkers.push(this.value);
    } else {
      var removeItem = this.value;
	  selWorkers = jQuery.grep(selWorkers, function(value) {
		  return value != removeItem;
	  });
    }
    console.log(selWorkers);

	var request = $.ajax({
	   url: "/workers/filter_workers_emails",
	   type: "GET",
	   data: {workers : selWorkers },
	   dataType: "html"
	}); //end var request

	request.done(function( msg ) {
		$("#worker_results").html( msg );
		//$("#recipient_email").val( msg );
	}); //end request.done

	//for sidebar view
	var req2 = $.ajax({
	   url: "/workers/right_sidebar_filt",
	   type: "GET",
	   data: {workers : selWorkers },
	   //dataType: "html"
	}); //end var request

	req2.done(function( res ) {
		//$("#worker_results").html( msg );
		res_trim = res.trim();
		$("#recipient_email").val(res_trim);
	}); //end request.done

 });

/*The code below updates the content of the edit modal each time you click on the edit button next to a worker in the panel
(fixes the issue that was causing the same worker information to appear in the edit modal unless you refreshed the page)
found via: http://stackoverflow.com/questions/13104919/twitter-bootstrap-modal-loads-wrong-remote-data
copied from: http://jsfiddle.net/Sherbrow/ThLYb/ */

$('[data-load-remote]').on('click',function(e) {
    e.preventDefault();
    var $this = $(this);
    var remote = $this.data('load-remote');
    if(remote) {
        $($this.data('remote-target')).load(remote);
    }
});

function copyEmailsToForm() {
	console.log("in copy emails");
	emails = document.getElementById("copy-emails-button").getAttribute("data-clipboard-text");
	console.log("emails: " + emails);
 	$("#recipient_email").val(emails);
 }

/*Zero Clipboard All Emails Copy Button */

var client = new ZeroClipboard( document.getElementById("copy-button") );

client.on( "ready", function( readyEvent ) {
  // alert( "ZeroClipboard SWF is ready!" );

  client.on( "aftercopy", function( event ) {
    // `this` === `client`
    // `event.target` === the element that was clicked
    //event.target.style.display = "none";
    alert("Copied emails to clipboard: " + event.data["text/plain"] );
  } );
} );

function insertFoundryLink(sel){
	link = sel.value.split(',');
	foundry_member_url = link[1].substring(2, link[1].length-2);
	console.log("link: " + link[1].substring(2, link[1].length-2));

	$("#foundry_member_url").val(foundry_member_url);
}

function hideRightPanels(){
	$("#hidePanelsLink").html('View Panels');
	$("#right-sidebar-wrapper").css('display', 'none');
	$("#hidePanelsLink").attr('onclick', 'viewRightPanels()');
	$("#email-form-div").removeClass("span8");
	$("#email-form-div").addClass("span12");
}

function viewRightPanels(){
	$("#hidePanelsLink").html('Hide Panels');
	$("#right-sidebar-wrapper").css('display', '');
	$("#hidePanelsLink").attr('onclick', 'hideRightPanels()');
	$("#email-form-div").removeClass("span12");
	$("#email-form-div").addClass("span8");
}
	<|endoftext|>


<commit_before>//= require ace/ace
//= require ace/mode-html
//= require ace/worker-html
//= require ace/mode-css
//= require ace/worker-css
//= require ace/mode-javascript
//= require ace/worker-javascript
//= require ace/theme-chrome
//= require angular.min
//= require angular-route.min

var app = angular.module("CodealiaIDE", [ 'ngRoute' ]);

app.config([ '$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/lessons/:lessonId', {
      template: function(params) {
        return '
Lesson ' + params.lessonId + '
';
      },
      controller: 'LessonsController'
    }).
      otherwise({
      redirectTo: '/lessons/1'
    });
  }
]);

app.controller('LessonsController', [ '$rootScope', '$scope', '$routeParams',
  function($rootScope, $scope, $routeParams) {
    $rootScope.lessonId = $routeParams.lessonId || '1';
    $rootScope.mode = $rootScope.mode || 'HTML';

    if ($routeParams.mode && $rootScope.mode !== $routeParams.mode) {
      $rootScope.mode = $routeParams.mode;
      console.log('You chose ' + $routeParams.mode);
    }

    $scope.generatePreview = $scope.generatePreview || function(editor) {
      $scope.preview.contents().find('body').html(editor.getValue());
    }

    $scope.init = $scope.init || function() {
      $scope.preview = $('#page-preview');

      $scope.editor = ace.edit('editor');
      editor = $scope.editor;

      editor.setTheme("ace/theme/chrome");
      editor.setFontSize(14);
      editor.getSession().setMode("ace/mode/html");
      editor.setHighlightActiveLine(false);
      editor.setShowPrintMargin(false);
      editor.commands.addCommand({
        name: "preview",
        bindKey: {
          win: "Shift-Enter",
          mac: "Shift-Enter"
        },
        exec: $scope.generatePreview
      });

      editor.getSession().on('change', function(e) {
        $scope.generatePreview(editor);
      });
    }
  }
]);
<commit_msg>add missing ace modules<commit_after>//= require ace/ace
//= require ace/worker-html
//= require ace/mode-html
//= require ace/worker-css
//= require ace/mode-css
//= require ace/worker-javascript
//= require ace/mode-javascript
//= require ace/theme-chrome
//= require angular.min
//= require angular-route.min

var app = angular.module("CodealiaIDE", [ 'ngRoute' ]);

app.config([ '$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/lessons/:lessonId', {
      template: function(params) {
        return '
Lesson ' + params.lessonId + '
';
      },
      controller: 'LessonsController'
    }).
      otherwise({
      redirectTo: '/lessons/1'
    });
  }
]);

app.controller('LessonsController', [ '$rootScope', '$scope', '$routeParams',
  function($rootScope, $scope, $routeParams) {
    $rootScope.lessonId = $routeParams.lessonId || '1';
    $rootScope.mode = $rootScope.mode || 'HTML';

    if ($routeParams.mode && $rootScope.mode !== $routeParams.mode) {
      $rootScope.mode = $routeParams.mode;
      console.log('You chose ' + $routeParams.mode);
    }

    $scope.generatePreview = $scope.generatePreview || function(editor) {
      $scope.preview.contents().find('body').html(editor.getValue());
    }

    $scope.init = $scope.init || function() {
      $scope.preview = $('#page-preview');

      $scope.editor = ace.edit('editor');
      editor = $scope.editor;

      editor.setTheme("ace/theme/chrome");
      editor.setFontSize(14);
      editor.getSession().setMode("ace/mode/html");
      editor.setHighlightActiveLine(false);
      editor.setShowPrintMargin(false);
      editor.commands.addCommand({
        name: "preview",
        bindKey: {
          win: "Shift-Enter",
          mac: "Shift-Enter"
        },
        exec: $scope.generatePreview
      });

      editor.getSession().on('change', function(e) {
        $scope.generatePreview(editor);
      });
    }
  }
]);
<|endoftext|>


<commit_before>function logError(fn) {
  return function (text) {
    try {
      fn(text);
    } catch (ex) {
      if (typeof console !== "undefined" && typeof console.error !== "undefined") {
        console.error('exception caught evaling RJS');
        console.error(ex);
        console.debug('script follows:', text);
      }
    }
    return text;
  };
}

jQuery.jsonPayload = function (text, fallback) {
  var parsed_json = jQuery.parseJSON(text);
  if (parsed_json['explorer']) {
    return ManageIQ.explorer.process(parsed_json); // ExplorerPresenter payload
  } else {
    return fallback(text);
  }
};

$.ajaxSetup({
  accepts: {
    json: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
  },
  contents: {
    json: /application\/json/
  },
  converters: {
    "text json": logError(function (text) {
      return jQuery.jsonPayload(text, function (text) { return jQuery.parseJSON(text); });
    }),
    "text script": logError(function (text) {
      if (text.match(/^{/)) {
        return jQuery.jsonPayload(text, function (text) { return text; });
      } else { // JavaScript payload
        jQuery.globalEval(text.slice('throw "error";'.length));
        return text;
      }
    }),
  }
});
<commit_msg>jquery_overrides.js - style cleanup<commit_after>function logError(fn) {
  return function (text) {
    try {
      fn(text);
    } catch (ex) {
      if (typeof console !== "undefined" && typeof console.error !== "undefined") {
        console.error('exception caught evaling RJS');
        console.error(ex);
        console.debug('script follows:', text);
      }
    }
    return text;
  };
}

jQuery.jsonPayload = function (text, fallback) {
  var parsed_json = jQuery.parseJSON(text);
  if (parsed_json.explorer) {
    return ManageIQ.explorer.process(parsed_json); // ExplorerPresenter payload
  } else {
    return fallback(text);
  }
};

$.ajaxSetup({
  accepts: {
    json: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript",
  },
  contents: {
    json: /application\/json/,
  },
  converters: {
    "text json": logError(function (text) {
      return jQuery.jsonPayload(text, function (text) {
        return jQuery.parseJSON(text);
      });
    }),
    "text script": logError(function (text) {
      if (text.match(/^{/)) {
        return jQuery.jsonPayload(text, function (text) {
          return text;
        });
      } else { // JavaScript payload
        jQuery.globalEval(text.slice('throw "error";'.length));
        return text;
      }
    }),
  },
});
<|endoftext|>


<commit_before>/*global jQuery,cyberDojo*/

var cyberDojo = (function(cd, $) {
  "use strict";

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.fileContentFor = function(filename) {
    return cd.id('file_content_for_' + filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.fileDiv = function(filename) {
    return cd.id(filename + '_div');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.makeNewFile = function(filename, content) {
    const div = $('<div>', {
      'class': 'filename_div',
      id: filename + '_div'
    });
    const text = $('<textarea>', {
      'class': 'file_content',
      'spellcheck': 'false',
      'data-filename': filename,
      name: 'file_content[' + filename + ']',
      id: 'file_content_for_' + filename
      //wrap: 'off'
    });
    // For some reason, setting wrap cannot be done as per the
    // commented out line above... when you create a new file in
    // FireFox 17.0.1 it still wraps at the textarea width.
    // So instead I do it like this, which works in FireFox?!
    text.attr('wrap', 'off');

    text.val(content);
    div.append(text);

    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadTestFile = function() {
    const filenames = cd.filenames();
    const filename = filenames[cd.testFilenameIndex(filenames)];
    cd.loadFile(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.testFilenameIndex = function(filenames) {
    // When starting and in file-knave navigation
    // the current file is sometimes not present.
    // (eg the file has been renamed/deleted).
    // When this happens, try to select a test file.
    for (let i = 0; i < filenames.length; i++) {
      const parts = filenames[i].toLowerCase().split('/');
      const filename = parts[parts.length - 1];
      if (filename.search('test') !== -1) {
        return i;
      }
    }
    return 0;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.newFileContent = function(filename, content) {
    const newFile = cd.makeNewFile(filename, content);
    $('#visible-files-container').append(newFile);
    cd.rebuildFilenameList();
    cd.switchEditorToCodeMirror(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.deleteFile = function(filename) {
    cd.fileDiv(filename).remove();
    cd.rebuildFilenameList();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.renameFile = (oldFilename, newFilename) => {
    cd.saveCodeFromIndividualSyntaxHighlightEditor(oldFilename);
    const oldFile = cd.fileContentFor(oldFilename);
    const content = oldFile.val();
    //const scrollTop = oldFile.scrollTop();
    //const scrollLeft = oldFile.scrollLeft();
    //const caretPos = oldFile.caret();
    cd.fileDiv(oldFilename).remove();
    cd.newFileContent(newFilename, content);
    cd.rebuildFilenameList();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.editorRefocus = function() {
    cd.loadFile(cd.currentFilename());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const cantBeRenamedOrDeleted = function(filename) {
    const filenames = [ 'cyber-dojo.sh', 'output' ];
    return cd.inArray(filename, filenames);
  };

  cd.setRenameAndDeleteButtons = function(filename) {
    const fileOps = $('#file-operations');
    const   newFile  = fileOps.find('#new');
    const renameFile = fileOps.find('#rename');
    const deleteFile = fileOps.find('#delete');
    const disable = function(node) { node.prop('disabled', true); };
    const enable  = function(node) { node.prop('disabled', false); };

    if (cantBeRenamedOrDeleted(filename)) {
      disable(renameFile);
      disable(deleteFile);
    } else {
      enable(renameFile);
      enable(deleteFile);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.selectFileInFileList = function(filename) {
    // Can't do $('radio_' + filename) because filename
    // could contain characters that aren't strictly legal
    // characters in a dom node id so I do this instead...
    const node = $('[id="radio_' + filename + '"]');
    const previousFilename = cd.currentFilename();
    const previous = $('[id="radio_' + previousFilename + '"]');
    cd.radioEntrySwitch(previous, node);
    cd.setRenameAndDeleteButtons(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.radioEntrySwitch = function(previous, current) {
    // Used in test-page, setup-page, and history-dialog
    if (previous != undefined) {
      previous.removeClass('selected');
    }
    current.addClass('selected');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  let theCurrentFilename = '';
  let theLastNonOutputFilename = '';

  cd.currentFilename = function() {
    return theCurrentFilename;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadFile = function(filename) {
    cd.fileDiv(cd.currentFilename()).hide();
    cd.selectFileInFileList(filename);
    cd.fileDiv(filename).show();

    cd.fileContentFor(filename).focus();
    cd.focusSyntaxHighlightEditor(filename);
    theCurrentFilename = filename;
    if (filename !== 'output') {
      theLastNonOutputFilename = filename;
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.toggleOutputFile = () => {
    if (cd.currentFilename() !== 'output') {
      cd.loadFile('output');
    } else {
      cd.loadFile(theLastNonOutputFilename);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  return cd;

})(cyberDojo || {}, jQuery);
<commit_msg>fix broken Alt-O hotkey which didn't work immediately after you deleted a file<commit_after>/*global jQuery,cyberDojo*/

var cyberDojo = (function(cd, $) {
  "use strict";

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.fileContentFor = function(filename) {
    return cd.id('file_content_for_' + filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.fileDiv = function(filename) {
    return cd.id(filename + '_div');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.makeNewFile = function(filename, content) {
    const div = $('<div>', {
      'class': 'filename_div',
      id: filename + '_div'
    });
    const text = $('<textarea>', {
      'class': 'file_content',
      'spellcheck': 'false',
      'data-filename': filename,
      name: 'file_content[' + filename + ']',
      id: 'file_content_for_' + filename
      //wrap: 'off'
    });
    // For some reason, setting wrap cannot be done as per the
    // commented out line above... when you create a new file in
    // FireFox 17.0.1 it still wraps at the textarea width.
    // So instead I do it like this, which works in FireFox?!
    text.attr('wrap', 'off');

    text.val(content);
    div.append(text);

    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const testFilename = () => {
    const filenames = cd.filenames();
    return filenames[cd.testFilenameIndex(filenames)];
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadTestFile = function() {
    cd.loadFile(testFilename());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.testFilenameIndex = function(filenames) {
    // When starting and in file-knave navigation
    // the current file is sometimes not present.
    // (eg the file has been renamed/deleted).
    // When this happens, try to select a test file.
    for (let i = 0; i < filenames.length; i++) {
      const parts = filenames[i].toLowerCase().split('/');
      const filename = parts[parts.length - 1];
      if (filename.search('test') !== -1) {
        return i;
      }
    }
    return 0;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.newFileContent = function(filename, content) {
    const newFile = cd.makeNewFile(filename, content);
    $('#visible-files-container').append(newFile);
    cd.rebuildFilenameList();
    cd.switchEditorToCodeMirror(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.deleteFile = function(filename) {
    cd.fileDiv(filename).remove();
    cd.rebuildFilenameList();
    theLastNonOutputFilename = testFilename();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.renameFile = (oldFilename, newFilename) => {
    cd.saveCodeFromIndividualSyntaxHighlightEditor(oldFilename);
    const oldFile = cd.fileContentFor(oldFilename);
    const content = oldFile.val();
    //const scrollTop = oldFile.scrollTop();
    //const scrollLeft = oldFile.scrollLeft();
    //const caretPos = oldFile.caret();
    cd.fileDiv(oldFilename).remove();
    cd.newFileContent(newFilename, content);
    cd.rebuildFilenameList();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.editorRefocus = function() {
    cd.loadFile(cd.currentFilename());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const cantBeRenamedOrDeleted = function(filename) {
    const filenames = [ 'cyber-dojo.sh', 'output' ];
    return cd.inArray(filename, filenames);
  };

  cd.setRenameAndDeleteButtons = function(filename) {
    const fileOps = $('#file-operations');
    const   newFile  = fileOps.find('#new');
    const renameFile = fileOps.find('#rename');
    const deleteFile = fileOps.find('#delete');
    const disable = function(node) { node.prop('disabled', true); };
    const enable  = function(node) { node.prop('disabled', false); };

    if (cantBeRenamedOrDeleted(filename)) {
      disable(renameFile);
      disable(deleteFile);
    } else {
      enable(renameFile);
      enable(deleteFile);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.selectFileInFileList = function(filename) {
    // Can't do $('radio_' + filename) because filename
    // could contain characters that aren't strictly legal
    // characters in a dom node id so I do this instead...
    const node = $('[id="radio_' + filename + '"]');
    const previousFilename = cd.currentFilename();
    const previous = $('[id="radio_' + previousFilename + '"]');
    cd.radioEntrySwitch(previous, node);
    cd.setRenameAndDeleteButtons(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.radioEntrySwitch = function(previous, current) {
    // Used in test-page, setup-page, and history-dialog
    if (previous != undefined) {
      previous.removeClass('selected');
    }
    current.addClass('selected');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  let theCurrentFilename = '';
  let theLastNonOutputFilename = '';

  cd.currentFilename = function() {
    return theCurrentFilename;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadFile = function(filename) {
    cd.fileDiv(cd.currentFilename()).hide();
    cd.selectFileInFileList(filename);
    cd.fileDiv(filename).show();

    cd.fileContentFor(filename).focus();
    cd.focusSyntaxHighlightEditor(filename);
    theCurrentFilename = filename;
    if (filename !== 'output') {
      theLastNonOutputFilename = filename;
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.toggleOutputFile = () => {
    if (cd.currentFilename() !== 'output') {
      cd.loadFile('output');
    } else {
      cd.loadFile(theLastNonOutputFilename);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  return cd;

})(cyberDojo || {}, jQuery);
<|endoftext|>


<commit_before>/*global jQuery,cyberDojo*/

var cyberDojo = (function(cd, $) {
  "use strict";

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filenames
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  let theCurrentFilename = '';
  let theLastNonOutputFilename = '';

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.currentFilename = () => {
    return theCurrentFilename;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.eachFilename = (f) => {
    cd.filenames().forEach(f);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.filenames = () => {
    // Gets the kata/edit page filenames. The review/show
    // page/dialog collects filenames in its own way.
    const filenames = [];
    const prefix = 'file_content_for_';
    $('textarea[id^=' + prefix + ']').each(function(_) {
      const id = $(this).attr('id');
      const filename = id.substr(prefix.length, id.length - prefix.length);
      filenames.push(filename);
    });
    return filenames;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.sortedFilenames = (filenames) => {
    // Controls the order of files in the filename-list
    // Used in two places
    // 1. kata/edit page to help show filename-list
    // 2. review/show page/dialog to help show filename-list
    return [].concat(hiFilenames(filenames), ['output'], loFilenames(filenames));
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Load a named file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadFile = (filename) => {
    fileDiv(cd.currentFilename()).hide();
    fileDiv(filename).show();

    selectFileInFileList(filename);
    cd.focusSyntaxHighlightEditor(filename);

    theCurrentFilename = filename;
    if (filename !== 'output') {
      theLastNonOutputFilename = filename;
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filename hot-key navigation
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/assets/javascripts/cyber-dojo_codemirror.js
  // See app/views/shared/_hotkeys.html.erb
  // Alt-J ==> loadNextFile()
  // Alt-K ==> loadPreviousFile()
  // Alt-O ==> toggleOutputFile()

  cd.loadNextFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index == -1) {
      const next = 0;
      cd.loadFile(hi[next]);
    } else {
      const next = (index + 1) % hi.length;
      cd.loadFile(hi[next]);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadPreviousFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index === 0 || index === -1) {
      const previous = hi.length - 1;
      cd.loadFile(hi[previous]);
    } else {
      const previous = index - 1;
      cd.loadFile(hi[previous]);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.toggleOutputFile = () => {
    if (cd.currentFilename() !== 'output') {
      cd.loadFile('output');
    } else {
      cd.loadFile(theLastNonOutputFilename);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // new-file, rename-file, delete-file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/views/kata/_file_new_rename_delete.html.erb
  // See app/views/kata/_files.html.erb
  // See app/views/kata/_run_tests.js.erb

  cd.newFile = (filename, content) => {
    const newFile = makeNewFile(filename, content);
    $('#visible-files-container').append(newFile);
    rebuildFilenameList();
    cd.switchEditorToCodeMirror(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.deleteFile = (filename) => {
    fileDiv(filename).remove();
    rebuildFilenameList();
    theLastNonOutputFilename = testFilename();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.renameFile = (oldFilename, newFilename) => {
    // This should restore the caret/cursor/selection
    // but it currently does not. See
    // https://github.com/cyber-dojo/web/issues/51
    const content = fileContent(oldFilename);
    cd.deleteFile(oldFilename);
    cd.newFile(newFilename, content);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Helpers
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.editorRefocus = () => {
    cd.loadFile(cd.currentFilename());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadTestFile = () => {
    cd.loadFile(testFilename());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.radioEntrySwitch = (previous, current) => {
    // Used in test-page, setup-pages, and history/diff-dialog
    // See app/assets/stylesheets/wide-list-item.scss
    if (previous != undefined) {
      previous.removeClass('selected');
    }
    current.addClass('selected');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.hashOfFile = (filename) => {
    const content = fileContent(filename);
    let hash = 0;
    for (let i = 0; i < content.length; ++i) {
      hash = (hash << 5) - hash + content.charCodeAt(i);
      hash &= hash;
    }
    return hash;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const fileContent = (filename) => {
    cd.saveCodeFromIndividualSyntaxHighlightEditor(filename);
    return jqElement('file_content_for_' + filename).val();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const jqElement = (name) => {
    return $('[id="' + name + '"]');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const fileDiv = (filename) => {
    return jqElement(filename + '_div');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const makeNewFile = (filename, content) => {
    const div = $('<div>', {
      'class': 'filename_div',
      id: filename + '_div'
    });
    const text = $('<textarea>', {
      'class': 'file_content',
      'spellcheck': 'false',
      'data-filename': filename,
      name: 'file_content[' + filename + ']',
      id: 'file_content_for_' + filename
      //wrap: 'off'
    });
    // For some reason, setting wrap cannot be done as per the
    // commented out line above... when you create a new file in
    // FireFox 17.0.1 it still wraps at the textarea width.
    // So instead I do it like this, which works in FireFox?!
    text.attr('wrap', 'off');

    text.val(content);
    div.append(text);

    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const testFilename = () => {
    const filenames = cd.filenames();
    return filenames[testFilenameIndex(filenames)];
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const testFilenameIndex = (filenames) => {
    // When starting and in file-knave navigation
    // the current file is sometimes not present.
    // (eg the file has been renamed/deleted).
    // When this happens, try to select a test file.
    for (let i = 0; i < filenames.length; i++) {
      const parts = filenames[i].toLowerCase().split('/');
      const filename = parts[parts.length - 1];
      if (filename.search('test') !== -1) {
        return i;
      }
    }
    return 0;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const setRenameAndDeleteButtons = (filename) => {
    const fileOps = $('#file-operations');
    const renameFile = fileOps.find('#rename');
    const deleteFile = fileOps.find('#delete');
    const disable = (node) => { node.prop('disabled', true ); };
    const enable  = (node) => { node.prop('disabled', false); };

    if (cantBeRenamedOrDeleted(filename)) {
      disable(renameFile);
      disable(deleteFile);
    } else {
      enable(renameFile);
      enable(deleteFile);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const cantBeRenamedOrDeleted = (filename) => {
    return cd.inArray(filename, [ 'cyber-dojo.sh', 'output' ]);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const rebuildFilenameList = () => {
    const filenameList = $('#filename-list');
    filenameList.empty();
    $.each(cd.sortedFilenames(cd.filenames()), (_, filename) => {
      filenameList.append(makeFileListEntry(filename));
    });
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const selectFileInFileList = (filename) => {
    // Can't do $('radio_' + filename) because filename
    // could contain characters that aren't strictly legal
    // characters in a dom node id so I do this instead...
    const node = $('[id="radio_' + filename + '"]');
    const previousFilename = cd.currentFilename();
    const previous = $('[id="radio_' + previousFilename + '"]');
    cd.radioEntrySwitch(previous, node);
    setRenameAndDeleteButtons(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const hiFilenames = (filenames) => {
    // Controls which filenames appear at the
    // top of the filename-list, above 'output'
    // Used in three places.
    // 1. kata/edit page to help show filename list
    // 2. kata/edit page in alt-j alt-k hotkeys
    // 3. review/show page/dialog to help show filename list
    let hi = [];
    $.each(filenames, function(_, filename) {
      if (isSourceFile(filename) || filename == 'instructions') {
        hi.push(filename);
      }
    });
    hi.sort();
    hi = hi.filter(item => item !== 'output')
    hi = hi.filter(item => item != 'cyber-dojo.sh')
    return hi;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const loFilenames = (filenames) => {
    // Controls which filenames appear at the
    // bottom of the filename list, below 'output'
    // Used in three places.
    // 1. kata/edit page to help show filename-list
    // 2. kata/edit page in Alt-j Alt-k hotkeys
    // 3. review/show page/dialog to help show filename-list
    let lo = [];
    $.each(filenames, (_, filename) => {
      if (!isSourceFile(filename) && filename != 'instructions') {
        lo.push(filename);
      }
    });
    lo.sort();
    lo = lo.filter(item => item !== 'output')
    return lo;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const isSourceFile = (filename) => {
    let match = false;
    $.each(cd.extensionFilenames(), (_, extension) => {
      // Shell test frameworks (eg shunit2) use .sh as their
      // filename extension but we don't want cyber-dojo.sh
      // in the hiFilenames() above output in the filename-list.
      if (filename.endsWith(extension) && filename != 'cyber-dojo.sh') {
        match = true;
      }
    });
    return match;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const makeFileListEntry = (filename) => {
    const div = $('<div>', {
      'class': 'filename',
           id: 'radio_' + filename,
         text: filename
    });
    if (cd.inArray(filename, cd.highlightFilenames())) {
      div.addClass('highlight');
    }
    div.click(() => { cd.loadFile(filename); });
    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  return cd;

})(cyberDojo || {}, jQuery);
<commit_msg>refactoring; function grouping in js file<commit_after>/*global jQuery,cyberDojo*/

var cyberDojo = (function(cd, $) {
  "use strict";

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filenames
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  let theCurrentFilename = '';
  let theLastNonOutputFilename = '';

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.currentFilename = () => {
    return theCurrentFilename;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.eachFilename = (f) => {
    cd.filenames().forEach(f);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.filenames = () => {
    // Gets the kata/edit page filenames. The review/show
    // page/dialog collects filenames in its own way.
    const filenames = [];
    const prefix = 'file_content_for_';
    $('textarea[id^=' + prefix + ']').each(function(_) {
      const id = $(this).attr('id');
      const filename = id.substr(prefix.length, id.length - prefix.length);
      filenames.push(filename);
    });
    return filenames;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.sortedFilenames = (filenames) => {
    // Controls the order of files in the filename-list
    // Used in two places
    // 1. kata/edit page to help show filename-list
    // 2. review/show page/dialog to help show filename-list
    return [].concat(hiFilenames(filenames), ['output'], loFilenames(filenames));
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Load a named file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadFile = (filename) => {
    fileDiv(cd.currentFilename()).hide();
    fileDiv(filename).show();

    selectFileInFileList(filename);
    cd.focusSyntaxHighlightEditor(filename);

    theCurrentFilename = filename;
    if (filename !== 'output') {
      theLastNonOutputFilename = filename;
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filename hot-key navigation
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/assets/javascripts/cyber-dojo_codemirror.js
  // See app/views/shared/_hotkeys.html.erb
  // Alt-J ==> loadNextFile()
  // Alt-K ==> loadPreviousFile()
  // Alt-O ==> toggleOutputFile()

  cd.loadNextFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index == -1) {
      const next = 0;
      cd.loadFile(hi[next]);
    } else {
      const next = (index + 1) % hi.length;
      cd.loadFile(hi[next]);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadPreviousFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index === 0 || index === -1) {
      const previous = hi.length - 1;
      cd.loadFile(hi[previous]);
    } else {
      const previous = index - 1;
      cd.loadFile(hi[previous]);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.toggleOutputFile = () => {
    if (cd.currentFilename() !== 'output') {
      cd.loadFile('output');
    } else {
      cd.loadFile(theLastNonOutputFilename);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // new-file, rename-file, delete-file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/views/kata/_file_new_rename_delete.html.erb
  // See app/views/kata/_files.html.erb
  // See app/views/kata/_run_tests.js.erb

  cd.newFile = (filename, content) => {
    const newFile = makeNewFile(filename, content);
    $('#visible-files-container').append(newFile);
    rebuildFilenameList();
    cd.switchEditorToCodeMirror(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.deleteFile = (filename) => {
    fileDiv(filename).remove();
    rebuildFilenameList();
    theLastNonOutputFilename = testFilename();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.renameFile = (oldFilename, newFilename) => {
    // This should restore the caret/cursor/selection
    // but it currently does not. See
    // https://github.com/cyber-dojo/web/issues/51
    const content = fileContent(oldFilename);
    cd.deleteFile(oldFilename);
    cd.newFile(newFilename, content);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Helpers
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.editorRefocus = () => {
    cd.loadFile(cd.currentFilename());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadTestFile = () => {
    cd.loadFile(testFilename());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.radioEntrySwitch = (previous, current) => {
    // Used in test-page, setup-pages, and history/diff-dialog
    // See app/assets/stylesheets/wide-list-item.scss
    if (previous != undefined) {
      previous.removeClass('selected');
    }
    current.addClass('selected');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.hashOfFile = (filename) => {
    const content = fileContent(filename);
    let hash = 0;
    for (let i = 0; i < content.length; ++i) {
      hash = (hash << 5) - hash + content.charCodeAt(i);
      hash &= hash;
    }
    return hash;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const fileContent = (filename) => {
    cd.saveCodeFromIndividualSyntaxHighlightEditor(filename);
    return jqElement('file_content_for_' + filename).val();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const jqElement = (name) => {
    return $('[id="' + name + '"]');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const fileDiv = (filename) => {
    return jqElement(filename + '_div');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const makeNewFile = (filename, content) => {
    const div = $('<div>', {
      'class': 'filename_div',
      id: filename + '_div'
    });
    const text = $('<textarea>', {
      'class': 'file_content',
      'spellcheck': 'false',
      'data-filename': filename,
      name: 'file_content[' + filename + ']',
      id: 'file_content_for_' + filename
      //wrap: 'off'
    });
    // For some reason, setting wrap cannot be done as per the
    // commented out line above... when you create a new file in
    // FireFox 17.0.1 it still wraps at the textarea width.
    // So instead I do it like this, which works in FireFox?!
    text.attr('wrap', 'off');

    text.val(content);
    div.append(text);

    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const testFilename = () => {
    const filenames = cd.filenames();
    return filenames[testFilenameIndex(filenames)];
  };

  const testFilenameIndex = (filenames) => {
    // When starting and in file-knave navigation
    // the current file is sometimes not present.
    // (eg the file has been renamed/deleted).
    // When this happens, try to select a test file.
    for (let i = 0; i < filenames.length; i++) {
      const parts = filenames[i].toLowerCase().split('/');
      const filename = parts[parts.length - 1];
      if (filename.search('test') !== -1) {
        return i;
      }
    }
    return 0;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const setRenameAndDeleteButtons = (filename) => {
    const fileOps = $('#file-operations');
    const renameFile = fileOps.find('#rename');
    const deleteFile = fileOps.find('#delete');
    const disable = (node) => { node.prop('disabled', true ); };
    const enable  = (node) => { node.prop('disabled', false); };

    if (cantBeRenamedOrDeleted(filename)) {
      disable(renameFile);
      disable(deleteFile);
    } else {
      enable(renameFile);
      enable(deleteFile);
    }
  };

  const cantBeRenamedOrDeleted = (filename) => {
    return cd.inArray(filename, [ 'cyber-dojo.sh', 'output' ]);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const rebuildFilenameList = () => {
    const filenameList = $('#filename-list');
    filenameList.empty();
    $.each(cd.sortedFilenames(cd.filenames()), (_, filename) => {
      filenameList.append(makeFileListEntry(filename));
    });
  };

  const makeFileListEntry = (filename) => {
    const div = $('<div>', {
      'class': 'filename',
           id: 'radio_' + filename,
         text: filename
    });
    if (cd.inArray(filename, cd.highlightFilenames())) {
      div.addClass('highlight');
    }
    div.click(() => { cd.loadFile(filename); });
    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const selectFileInFileList = (filename) => {
    // Can't do $('radio_' + filename) because filename
    // could contain characters that aren't strictly legal
    // characters in a dom node id so I do this instead...
    const node = $('[id="radio_' + filename + '"]');
    const previousFilename = cd.currentFilename();
    const previous = $('[id="radio_' + previousFilename + '"]');
    cd.radioEntrySwitch(previous, node);
    setRenameAndDeleteButtons(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const hiFilenames = (filenames) => {
    // Controls which filenames appear at the
    // top of the filename-list, above 'output'
    // Used in three places.
    // 1. kata/edit page to help show filename list
    // 2. kata/edit page in alt-j alt-k hotkeys
    // 3. review/show page/dialog to help show filename list
    let hi = [];
    $.each(filenames, function(_, filename) {
      if (isSourceFile(filename) || filename == 'instructions') {
        hi.push(filename);
      }
    });
    hi.sort();
    hi = hi.filter(item => item !== 'output')
    hi = hi.filter(item => item != 'cyber-dojo.sh')
    return hi;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const loFilenames = (filenames) => {
    // Controls which filenames appear at the
    // bottom of the filename list, below 'output'
    // Used in three places.
    // 1. kata/edit page to help show filename-list
    // 2. kata/edit page in Alt-j Alt-k hotkeys
    // 3. review/show page/dialog to help show filename-list
    let lo = [];
    $.each(filenames, (_, filename) => {
      if (!isSourceFile(filename) && filename != 'instructions') {
        lo.push(filename);
      }
    });
    lo.sort();
    lo = lo.filter(item => item !== 'output')
    return lo;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const isSourceFile = (filename) => {
    let match = false;
    $.each(cd.extensionFilenames(), (_, extension) => {
      // Shell test frameworks (eg shunit2) use .sh as their
      // filename extension but we don't want cyber-dojo.sh
      // in the hiFilenames() above output in the filename-list.
      if (filename.endsWith(extension) && filename != 'cyber-dojo.sh') {
        match = true;
      }
    });
    return match;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  return cd;

})(cyberDojo || {}, jQuery);
<|endoftext|>


<commit_before>/*global jQuery,cyberDojo*/

var cyberDojo = (function(cd, $) {
  "use strict";

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filenames
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  let theCurrentFilename = '';
  let theLastNonOutputFilename = '';

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Load a named file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadFile = (filename) => {
    fileDiv(cd.currentFilename()).hide();
    fileDiv(filename).show();

    selectFileInFileList(filename);
    cd.focusSyntaxHighlightEditor(filename);

    theCurrentFilename = filename;
    if (filename !== 'output') {
      theLastNonOutputFilename = filename;
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.currentFilename = () => {
    return theCurrentFilename;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.eachFilename = (f) => {
    cd.filenames().forEach(f);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.filenames = () => {
    // Gets the kata/edit page filenames. The review/show
    // page/dialog collects filenames in its own way.
    const filenames = [];
    const prefix = 'file_content_for_';
    $('textarea[id^=' + prefix + ']').each(function(_) {
      const id = $(this).attr('id');
      const filename = id.substr(prefix.length, id.length - prefix.length);
      filenames.push(filename);
    });
    return filenames;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.sortedFilenames = (filenames) => {
    // Controls the order of files in the filename-list
    // Used in two places
    // 1. kata/edit page to help show filename-list
    // 2. review/show page/dialog to help show filename-list
    return [].concat(hiFilenames(filenames), ['output'], loFilenames(filenames));
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filename hot-key navigation
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/assets/javascripts/cyber-dojo_codemirror.js
  // See app/views/shared/_hotkeys.html.erb
  // Alt-J ==> loadNextFile()
  // Alt-K ==> loadPreviousFile()
  // Alt-O ==> toggleOutputFile()

  cd.loadNextFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index == -1) {
      const next = 0;
      cd.loadFile(hi[next]);
    } else {
      const next = (index + 1) % hi.length;
      cd.loadFile(hi[next]);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadPreviousFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index === 0 || index === -1) {
      const previous = hi.length - 1;
      cd.loadFile(hi[previous]);
    } else {
      const previous = index - 1;
      cd.loadFile(hi[previous]);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.toggleOutputFile = () => {
    if (cd.currentFilename() !== 'output') {
      cd.loadFile('output');
    } else {
      cd.loadFile(theLastNonOutputFilename);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // new-file, rename-file, delete-file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/views/kata/_file_new_rename_delete.html.erb
  // See app/views/kata/_files.html.erb
  // See app/views/kata/_run_tests.js.erb

  cd.newFile = (filename, content) => {
    const newFile = makeNewFile(filename, content);
    $('#visible-files-container').append(newFile);
    rebuildFilenameList();
    cd.switchEditorToCodeMirror(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.deleteFile = (filename) => {
    fileDiv(filename).remove();
    rebuildFilenameList();
    theLastNonOutputFilename = testFilename();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.renameFile = (oldFilename, newFilename) => {
    // This should restore the caret/cursor/selection
    // but it currently does not. See
    // https://github.com/cyber-dojo/web/issues/51
    const content = fileContent(oldFilename);
    cd.deleteFile(oldFilename);
    cd.newFile(newFilename, content);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Helpers
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.editorRefocus = () => {
    cd.loadFile(cd.currentFilename());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadTestFile = () => {
    cd.loadFile(testFilename());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.radioEntrySwitch = (previous, current) => {
    // Used in test-page, setup-pages, and history/diff-dialog
    // See app/assets/stylesheets/wide-list-item.scss
    if (previous != undefined) {
      previous.removeClass('selected');
    }
    current.addClass('selected');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.hashOfFile = (filename) => {
    const content = fileContent(filename);
    let hash = 0;
    for (let i = 0; i < content.length; ++i) {
      hash = (hash << 5) - hash + content.charCodeAt(i);
      hash &= hash;
    }
    return hash;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const fileContent = (filename) => {
    cd.saveCodeFromIndividualSyntaxHighlightEditor(filename);
    return jqElement('file_content_for_' + filename).val();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const jqElement = (name) => {
    return $('[id="' + name + '"]');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const fileDiv = (filename) => {
    return jqElement(filename + '_div');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const makeNewFile = (filename, content) => {
    const div = $('<div>', {
      'class': 'filename_div',
      id: filename + '_div'
    });
    const text = $('<textarea>', {
      'class': 'file_content',
      'spellcheck': 'false',
      'data-filename': filename,
      name: 'file_content[' + filename + ']',
      id: 'file_content_for_' + filename
      //wrap: 'off'
    });
    // For some reason, setting wrap cannot be done as per the
    // commented out line above... when you create a new file in
    // FireFox 17.0.1 it still wraps at the textarea width.
    // So instead I do it like this, which works in FireFox?!
    text.attr('wrap', 'off');

    text.val(content);
    div.append(text);

    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const testFilename = () => {
    const filenames = cd.filenames();
    return filenames[testFilenameIndex(filenames)];
  };

  const testFilenameIndex = (filenames) => {
    // When starting and in file-knave navigation
    // the current file is sometimes not present.
    // (eg the file has been renamed/deleted).
    // When this happens, try to select a test file.
    for (let i = 0; i < filenames.length; i++) {
      const parts = filenames[i].toLowerCase().split('/');
      const filename = parts[parts.length - 1];
      if (filename.search('test') !== -1) {
        return i;
      }
      if (filename.search('spec') !== -1) {
        return i;
      }
    }
    return 0;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const setRenameAndDeleteButtons = (filename) => {
    const fileOps = $('#file-operations');
    const renameFile = fileOps.find('#rename');
    const deleteFile = fileOps.find('#delete');
    const disable = (node) => { node.prop('disabled', true ); };
    const enable  = (node) => { node.prop('disabled', false); };

    if (cantBeRenamedOrDeleted(filename)) {
      disable(renameFile);
      disable(deleteFile);
    } else {
      enable(renameFile);
      enable(deleteFile);
    }
  };

  const cantBeRenamedOrDeleted = (filename) => {
    return cd.inArray(filename, [ 'cyber-dojo.sh', 'output' ]);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const rebuildFilenameList = () => {
    const filenameList = $('#filename-list');
    filenameList.empty();
    $.each(cd.sortedFilenames(cd.filenames()), (_, filename) => {
      filenameList.append(makeFileListEntry(filename));
    });
  };

  const makeFileListEntry = (filename) => {
    const div = $('<div>', {
      'class': 'filename',
           id: 'radio_' + filename,
         text: filename
    });
    if (cd.inArray(filename, cd.highlightFilenames())) {
      div.addClass('highlight');
    }
    div.click(() => { cd.loadFile(filename); });
    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const selectFileInFileList = (filename) => {
    // Can't do $('radio_' + filename) because filename
    // could contain characters that aren't strictly legal
    // characters in a dom node id so I do this instead...
    const node = $('[id="radio_' + filename + '"]');
    const previousFilename = cd.currentFilename();
    const previous = $('[id="radio_' + previousFilename + '"]');
    cd.radioEntrySwitch(previous, node);
    setRenameAndDeleteButtons(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const hiFilenames = (filenames) => {
    // Controls which filenames appear at the
    // top of the filename-list, above 'output'
    // Used in three places.
    // 1. kata/edit page to help show filename list
    // 2. kata/edit page in alt-j alt-k hotkeys
    // 3. review/show page/dialog to help show filename list
    let hi = [];
    $.each(filenames, function(_, filename) {
      if (isSourceFile(filename) || filename == 'instructions') {
        hi.push(filename);
      }
    });
    hi.sort();
    hi = hi.filter(item => item !== 'output')
    hi = hi.filter(item => item != 'cyber-dojo.sh')
    return hi;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const loFilenames = (filenames) => {
    // Controls which filenames appear at the
    // bottom of the filename list, below 'output'
    // Used in three places.
    // 1. kata/edit page to help show filename-list
    // 2. kata/edit page in Alt-j Alt-k hotkeys
    // 3. review/show page/dialog to help show filename-list
    let lo = [];
    $.each(filenames, (_, filename) => {
      if (!isSourceFile(filename) && filename != 'instructions') {
        lo.push(filename);
      }
    });
    lo.sort();
    lo = lo.filter(item => item !== 'output')
    return lo;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const isSourceFile = (filename) => {
    let match = false;
    $.each(cd.extensionFilenames(), (_, extension) => {
      // Shell test frameworks (eg shunit2) use .sh as their
      // filename extension but we don't want cyber-dojo.sh
      // in the hiFilenames() above output in the filename-list.
      if (filename.endsWith(extension) && filename != 'cyber-dojo.sh') {
        match = true;
      }
    });
    return match;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  return cd;

})(cyberDojo || {}, jQuery);
<commit_msg>refactoring; collapse testFilename() and testFilenameIndex() into one function<commit_after>/*global jQuery,cyberDojo*/

var cyberDojo = (function(cd, $) {
  "use strict";

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filenames
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  let theCurrentFilename = '';
  let theLastNonOutputFilename = '';

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Load a named file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadFile = (filename) => {
    fileDiv(cd.currentFilename()).hide();
    fileDiv(filename).show();

    selectFileInFileList(filename);
    cd.focusSyntaxHighlightEditor(filename);

    theCurrentFilename = filename;
    if (filename !== 'output') {
      theLastNonOutputFilename = filename;
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.currentFilename = () => {
    return theCurrentFilename;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.eachFilename = (f) => {
    cd.filenames().forEach(f);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.filenames = () => {
    // Gets the kata/edit page filenames. The review/show
    // page/dialog collects filenames in its own way.
    const filenames = [];
    const prefix = 'file_content_for_';
    $('textarea[id^=' + prefix + ']').each(function(_) {
      const id = $(this).attr('id');
      const filename = id.substr(prefix.length, id.length - prefix.length);
      filenames.push(filename);
    });
    return filenames;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.sortedFilenames = (filenames) => {
    // Controls the order of files in the filename-list
    // Used in two places
    // 1. kata/edit page to help show filename-list
    // 2. review/show page/dialog to help show filename-list
    return [].concat(hiFilenames(filenames), ['output'], loFilenames(filenames));
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filename hot-key navigation
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/assets/javascripts/cyber-dojo_codemirror.js
  // See app/views/shared/_hotkeys.html.erb
  // Alt-J ==> loadNextFile()
  // Alt-K ==> loadPreviousFile()
  // Alt-O ==> toggleOutputFile()

  cd.loadNextFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index == -1) {
      const next = 0;
      cd.loadFile(hi[next]);
    } else {
      const next = (index + 1) % hi.length;
      cd.loadFile(hi[next]);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadPreviousFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index === 0 || index === -1) {
      const previous = hi.length - 1;
      cd.loadFile(hi[previous]);
    } else {
      const previous = index - 1;
      cd.loadFile(hi[previous]);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.toggleOutputFile = () => {
    if (cd.currentFilename() !== 'output') {
      cd.loadFile('output');
    } else {
      cd.loadFile(theLastNonOutputFilename);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // new-file, rename-file, delete-file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/views/kata/_file_new_rename_delete.html.erb
  // See app/views/kata/_files.html.erb
  // See app/views/kata/_run_tests.js.erb

  cd.newFile = (filename, content) => {
    const newFile = makeNewFile(filename, content);
    $('#visible-files-container').append(newFile);
    rebuildFilenameList();
    cd.switchEditorToCodeMirror(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.deleteFile = (filename) => {
    fileDiv(filename).remove();
    rebuildFilenameList();
    theLastNonOutputFilename = testFilename();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.renameFile = (oldFilename, newFilename) => {
    // This should restore the caret/cursor/selection
    // but it currently does not. See
    // https://github.com/cyber-dojo/web/issues/51
    const content = fileContent(oldFilename);
    cd.deleteFile(oldFilename);
    cd.newFile(newFilename, content);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Helpers
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.editorRefocus = () => {
    cd.loadFile(cd.currentFilename());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadTestFile = () => {
    cd.loadFile(testFilename());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.radioEntrySwitch = (previous, current) => {
    // Used in test-page, setup-pages, and history/diff-dialog
    // See app/assets/stylesheets/wide-list-item.scss
    if (previous != undefined) {
      previous.removeClass('selected');
    }
    current.addClass('selected');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.hashOfFile = (filename) => {
    const content = fileContent(filename);
    let hash = 0;
    for (let i = 0; i < content.length; ++i) {
      hash = (hash << 5) - hash + content.charCodeAt(i);
      hash &= hash;
    }
    return hash;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const fileContent = (filename) => {
    cd.saveCodeFromIndividualSyntaxHighlightEditor(filename);
    return jqElement('file_content_for_' + filename).val();
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const jqElement = (name) => {
    return $('[id="' + name + '"]');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const fileDiv = (filename) => {
    return jqElement(filename + '_div');
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const makeNewFile = (filename, content) => {
    const div = $('<div>', {
      'class': 'filename_div',
      id: filename + '_div'
    });
    const text = $('<textarea>', {
      'class': 'file_content',
      'spellcheck': 'false',
      'data-filename': filename,
      name: 'file_content[' + filename + ']',
      id: 'file_content_for_' + filename
      //wrap: 'off'
    });
    // For some reason, setting wrap cannot be done as per the
    // commented out line above... when you create a new file in
    // FireFox 17.0.1 it still wraps at the textarea width.
    // So instead I do it like this, which works in FireFox?!
    text.attr('wrap', 'off');

    text.val(content);
    div.append(text);

    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const testFilename = () => {
    // When starting and in filename-list navigation
    // and when the current file is deleted,
    // try to select a test file.
    const filenames = cd.filenames();
    for (let i = 0; i < filenames.length; i++) {
      // split into dir names and filename
      const parts = filenames[i].toLowerCase().split('/');
      const filename = parts[parts.length - 1];
      if (filename.search('test') !== -1) {
        return filename;
      }
      if (filename.search('spec') !== -1) {
        return filename;
      }
    }
    return filenames[0];
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const setRenameAndDeleteButtons = (filename) => {
    const fileOps = $('#file-operations');
    const renameFile = fileOps.find('#rename');
    const deleteFile = fileOps.find('#delete');
    const disable = (node) => { node.prop('disabled', true ); };
    const enable  = (node) => { node.prop('disabled', false); };

    if (cantBeRenamedOrDeleted(filename)) {
      disable(renameFile);
      disable(deleteFile);
    } else {
      enable(renameFile);
      enable(deleteFile);
    }
  };

  const cantBeRenamedOrDeleted = (filename) => {
    return cd.inArray(filename, [ 'cyber-dojo.sh', 'output' ]);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const rebuildFilenameList = () => {
    const filenameList = $('#filename-list');
    filenameList.empty();
    $.each(cd.sortedFilenames(cd.filenames()), (_, filename) => {
      filenameList.append(makeFileListEntry(filename));
    });
  };

  const makeFileListEntry = (filename) => {
    const div = $('<div>', {
      'class': 'filename',
           id: 'radio_' + filename,
         text: filename
    });
    if (cd.inArray(filename, cd.highlightFilenames())) {
      div.addClass('highlight');
    }
    div.click(() => { cd.loadFile(filename); });
    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const selectFileInFileList = (filename) => {
    // Can't do $('radio_' + filename) because filename
    // could contain characters that aren't strictly legal
    // characters in a dom node id so I do this instead...
    const node = $('[id="radio_' + filename + '"]');
    const previousFilename = cd.currentFilename();
    const previous = $('[id="radio_' + previousFilename + '"]');
    cd.radioEntrySwitch(previous, node);
    setRenameAndDeleteButtons(filename);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const hiFilenames = (filenames) => {
    // Controls which filenames appear at the
    // top of the filename-list, above 'output'
    // Used in three places.
    // 1. kata/edit page to help show filename list
    // 2. kata/edit page in alt-j alt-k hotkeys
    // 3. review/show page/dialog to help show filename list
    let hi = [];
    $.each(filenames, function(_, filename) {
      if (isSourceFile(filename) || filename == 'instructions') {
        hi.push(filename);
      }
    });
    hi.sort();
    hi = hi.filter(item => item !== 'output')
    hi = hi.filter(item => item != 'cyber-dojo.sh')
    return hi;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const loFilenames = (filenames) => {
    // Controls which filenames appear at the
    // bottom of the filename list, below 'output'
    // Used in three places.
    // 1. kata/edit page to help show filename-list
    // 2. kata/edit page in Alt-j Alt-k hotkeys
    // 3. review/show page/dialog to help show filename-list
    let lo = [];
    $.each(filenames, (_, filename) => {
      if (!isSourceFile(filename) && filename != 'instructions') {
        lo.push(filename);
      }
    });
    lo.sort();
    lo = lo.filter(item => item !== 'output')
    return lo;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const isSourceFile = (filename) => {
    let match = false;
    $.each(cd.extensionFilenames(), (_, extension) => {
      // Shell test frameworks (eg shunit2) use .sh as their
      // filename extension but we don't want cyber-dojo.sh
      // in the hiFilenames() above output in the filename-list.
      if (filename.endsWith(extension) && filename != 'cyber-dojo.sh') {
        match = true;
      }
    });
    return match;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  return cd;

})(cyberDojo || {}, jQuery);
<|endoftext|>


<commit_before>import React from 'react';
import _ from 'lodash';
import Bar from '../components/Bar';
import {colors} from '../helpers/Theme';

// Translate null values
function cleanNulls(students) {
  return students.map((student) => {
    return {
      ...student,
      race: (student.race) ? student.race : 'Unknown'
    };
  });
}

const orderedGrades = [ 'PK', 'KF', '1', '2', '3', '4', '5', '6', '7', '8' ];

// Helpers
// Returns [{key, count}]
const countBy = function(students, key) {
  return _.map(_.groupBy(students, key), (students, value) => {
    return {[key]: value, count: students.length};
  });
};

// Returns {foo: count, bar: count}
const makeCountMap = function(students, key) {
  return countBy(students, key).reduce((map, item) => {
    map[item[key]] = item.count;
    return map;
  }, {});
};

// Returns [{key, count}], filling in zero values for `allValues`
// if they're not present.
const completeCountBy = function(students, key, allValues) {
  return allValues.map((value) => {
    const matches = _.where(students, { [key]: value });
    return { [key]: value, count: matches.length };
  });
};

// Returns percent as integer 0-100
const percentageOf = function(students, key, value) {
  const countMap = makeCountMap(students, key);
  const total = Object.keys(countMap).reduce((sum, key) => {
    return sum + countMap[key];
  }, 0);
  return (total === 0) ?
    0
    : 100 * (countMap[value] || 0) / total;
};

// Returns percent as integer 0-100
const percentageOverLimit = function(students, key, limit) {
  const total = students.length;
  const numberOverLimit = students.filter((student) => {
    return student[key] > limit;
  }).length;

  return (total === 0) ?
    0
    : 100 * numberOverLimit / total;
};

const styles = {
  bar: {
    color: 'black',
    backgroundColor: '#ccc',
    borderLeft: '1px solid #aaa'
  }
};

export default class Breakdown extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      gradeFilter: _.first(orderedGrades)
    };

    this.onGradeClicked = this.onGradeClicked.bind(this);
  }

  onGradeClicked(grade) {
    this.setState({ gradeFilter: grade });
  }

  render() {
    // Clean and filter
    const {gradeFilter} = this.state;
    const rawStudents = this.props.students;
    const cleanedStudents = cleanNulls(rawStudents);
    const students = (gradeFilter === null)
      ? cleanedStudents
      : cleanedStudents.filter(student => student.grade === gradeFilter);
    const allRaces = _.sortBy(_.uniq(_.map(cleanedStudents, 'race')));

    // Compute
    const studentsByHomeroom = _.map(_.groupBy(students, 'homeroom_name'), (students, homeroomName) => {
      return {
        homeroomName,
        students,
        homeroomId: students[0].homeroom_id
      };
    });
    const statsForHomerooms = studentsByHomeroom.map(({homeroomName, homeroomId, students}) => {
      const grades = _.uniq(_.map(students, 'grade'));
      const raceGroups = completeCountBy(students, 'race', allRaces);
      const lunchMap = makeCountMap(students, 'free_reduced_lunch');
      const lunchPercent = 100 - percentageOf(students, 'free_reduced_lunch', 'Not Eligible');
      const hispanicPercent = percentageOf(students, 'hispanic_latino', true);
      const disabilityPercent = 100 - percentageOf(students, 'disability', null);
      const lepPercent = 100 - percentageOf(students, 'limited_english_proficiency', 'Fluent');
      const colorPercent = 100 * students.filter(s => s.hispanic_latino || s.race !== 'White').length / students.length;
      const overOneWeekAbsentPercent = percentageOverLimit(students, 'absences_count', 5);
      const overOneWeekTardyPercent = percentageOverLimit(students, 'tardies_count', 5);
      const hasDisciplinePercent = percentageOverLimit(students, 'discipline_incidents_count', 0)

      return {
        homeroomName,
        homeroomId,
        grades,
        raceGroups,
        lunchMap,
        lunchPercent,
        disabilityPercent,
        lepPercent,
        hispanicPercent,
        colorPercent,
        overOneWeekAbsentPercent,
        overOneWeekTardyPercent,
        hasDisciplinePercent,
        studentCount: students.length
      };
    });

    // Render
    // Keep colors consistent
    const color = d3.scale.ordinal()
      .domain(_.sortBy(allRaces, race => race.length))
      // .range(['#ffffe0','#ffcb91','#fe906a','#e75758','#c0223b','#8b0000']);
      // .range(['#66c2a5','#fc8d62','#8da0cb','#e78ac3','#a6d854','#ffd92f','#e5c494','#b3b3b3']);
      .range(['#1b9e77','#d95f02','#7570b3','#e7298a','#66a61e','#e6ab02','#a6761d','#666666']);
    const sortedStatsForHomerooms = _.sortBy(statsForHomerooms, ({grades, studentCount}) => {
      if (grades.length === 1) return (100 * orderedGrades.indexOf(grades[0])) - studentCount;
      if (grades.length > 1) return -1000 - studentCount;
      return -2000 - studentCount;
    });

    return (

          {this.renderTitle('Select a grade')}

            {orderedGrades.map((grade) => {
              return <button
                style={{
                  padding: 15,
                  paddingTop: 10,
                  paddingBottom: 10,
                  cursor: 'pointer',
                  margin: 3,
                  width: '5em',
                  border: '1px solid #ccc',
                  backgroundColor: (grade === gradeFilter) ? colors.selection : '#eee'
                }}
                key={grade}
                onClick={this.onGradeClicked.bind(this, grade)}>{grade}</button>;
            })}

This excludes mixed grade homerooms.

          {this.renderTitle('Compare across homeroom')}

                  Homeroom

                  Size

                  > 5 Absences

                  > 5 Tardies

                  > 0 Discipline

                  Disability

                  Limited English

                  Low income

                  Racial composition

                Students of color
                Hispanic

              {sortedStatsForHomerooms.map((statsForHomeroom) => {
                const {
                  homeroomName,
                  homeroomId,
                  studentCount,
                  raceGroups,
                  lunchPercent,
                  hispanicPercent,
                  disabilityPercent,
                  lepPercent,
                  colorPercent,
                  overOneWeekAbsentPercent,
                  overOneWeekTardyPercent,
                  hasDisciplinePercent,
                } = statsForHomeroom;
                const total = _.map(raceGroups, 'count').reduce((sum, a) => {
                  return sum + a;
                }, 0);

                return (

                      <a href={`/homerooms/${homeroomId}`}>{homeroomName}

                    {studentCount}

                    {/* Attendance / Discipline */}

                      <Bar percent={overOneWeekAbsentPercent} styles={styles.bar} threshold={10} />

                      <Bar percent={overOneWeekTardyPercent} styles={styles.bar} threshold={10} />

                      <Bar percent={hasDisciplinePercent} styles={styles.bar} threshold={10} />

                    {/* Demographics */}

                      <Bar percent={disabilityPercent} styles={styles.bar} threshold={10} />

                      <Bar percent={lepPercent} styles={styles.bar} threshold={10} />

                      <Bar percent={lunchPercent} styles={styles.bar} threshold={10} />

                    {/* Race */}

                        {_.sortBy(raceGroups, 'race').map((group) => {
                          const {race, count} = group;
                          const percent = 100 * count / total;
                          const title = Math.round(percent) + '% ' + race;
                          if (percent === 0) return null;

                          return (
                            <Bar
                              key={race}
                              percent={percent}
                              threshold={10}
                              title={title}
                              styles={{backgroundColor: color(race)}}
                              innerStyles={{color: 'white'}} />
                          );
                        })}

                      <Bar percent={colorPercent} styles={styles.bar} threshold={10} />

                      <Bar percent={hispanicPercent} styles={styles.bar} threshold={10} />

                );
              })}

          {this.renderTitle('Color key')}

            {allRaces.map((race) => {
              return
{race}
;
            })}

    );
  }

  renderTitle(title) {
    return
{title}
;
  }

}

Breakdown.propTypes = {
  students: React.PropTypes.arrayOf(React.PropTypes.shape({
    race: React.PropTypes.string,
    disability: React.PropTypes.string,
    limited_english_proficiency: React.PropTypes.string,
    gender: React.PropTypes.string,
    grade: React.PropTypes.string,
    free_reduced_lunch: React.PropTypes.string,
    hispanic_latino: React.PropTypes.bool,
    homeroom_id: React.PropTypes.number,
    homeroom_name: React.PropTypes.string
  })).isRequired
};
<commit_msg>Add semicolon<commit_after>import React from 'react';
import _ from 'lodash';
import Bar from '../components/Bar';
import {colors} from '../helpers/Theme';

// Translate null values
function cleanNulls(students) {
  return students.map((student) => {
    return {
      ...student,
      race: (student.race) ? student.race : 'Unknown'
    };
  });
}

const orderedGrades = [ 'PK', 'KF', '1', '2', '3', '4', '5', '6', '7', '8' ];

// Helpers
// Returns [{key, count}]
const countBy = function(students, key) {
  return _.map(_.groupBy(students, key), (students, value) => {
    return {[key]: value, count: students.length};
  });
};

// Returns {foo: count, bar: count}
const makeCountMap = function(students, key) {
  return countBy(students, key).reduce((map, item) => {
    map[item[key]] = item.count;
    return map;
  }, {});
};

// Returns [{key, count}], filling in zero values for `allValues`
// if they're not present.
const completeCountBy = function(students, key, allValues) {
  return allValues.map((value) => {
    const matches = _.where(students, { [key]: value });
    return { [key]: value, count: matches.length };
  });
};

// Returns percent as integer 0-100
const percentageOf = function(students, key, value) {
  const countMap = makeCountMap(students, key);
  const total = Object.keys(countMap).reduce((sum, key) => {
    return sum + countMap[key];
  }, 0);
  return (total === 0) ?
    0
    : 100 * (countMap[value] || 0) / total;
};

// Returns percent as integer 0-100
const percentageOverLimit = function(students, key, limit) {
  const total = students.length;
  const numberOverLimit = students.filter((student) => {
    return student[key] > limit;
  }).length;

  return (total === 0) ?
    0
    : 100 * numberOverLimit / total;
};

const styles = {
  bar: {
    color: 'black',
    backgroundColor: '#ccc',
    borderLeft: '1px solid #aaa'
  }
};

export default class Breakdown extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      gradeFilter: _.first(orderedGrades)
    };

    this.onGradeClicked = this.onGradeClicked.bind(this);
  }

  onGradeClicked(grade) {
    this.setState({ gradeFilter: grade });
  }

  render() {
    // Clean and filter
    const {gradeFilter} = this.state;
    const rawStudents = this.props.students;
    const cleanedStudents = cleanNulls(rawStudents);
    const students = (gradeFilter === null)
      ? cleanedStudents
      : cleanedStudents.filter(student => student.grade === gradeFilter);
    const allRaces = _.sortBy(_.uniq(_.map(cleanedStudents, 'race')));

    // Compute
    const studentsByHomeroom = _.map(_.groupBy(students, 'homeroom_name'), (students, homeroomName) => {
      return {
        homeroomName,
        students,
        homeroomId: students[0].homeroom_id
      };
    });
    const statsForHomerooms = studentsByHomeroom.map(({homeroomName, homeroomId, students}) => {
      const grades = _.uniq(_.map(students, 'grade'));
      const raceGroups = completeCountBy(students, 'race', allRaces);
      const lunchMap = makeCountMap(students, 'free_reduced_lunch');
      const lunchPercent = 100 - percentageOf(students, 'free_reduced_lunch', 'Not Eligible');
      const hispanicPercent = percentageOf(students, 'hispanic_latino', true);
      const disabilityPercent = 100 - percentageOf(students, 'disability', null);
      const lepPercent = 100 - percentageOf(students, 'limited_english_proficiency', 'Fluent');
      const colorPercent = 100 * students.filter(s => s.hispanic_latino || s.race !== 'White').length / students.length;
      const overOneWeekAbsentPercent = percentageOverLimit(students, 'absences_count', 5);
      const overOneWeekTardyPercent = percentageOverLimit(students, 'tardies_count', 5);
      const hasDisciplinePercent = percentageOverLimit(students, 'discipline_incidents_count', 0);

      return {
        homeroomName,
        homeroomId,
        grades,
        raceGroups,
        lunchMap,
        lunchPercent,
        disabilityPercent,
        lepPercent,
        hispanicPercent,
        colorPercent,
        overOneWeekAbsentPercent,
        overOneWeekTardyPercent,
        hasDisciplinePercent,
        studentCount: students.length
      };
    });

    // Render
    // Keep colors consistent
    const color = d3.scale.ordinal()
      .domain(_.sortBy(allRaces, race => race.length))
      // .range(['#ffffe0','#ffcb91','#fe906a','#e75758','#c0223b','#8b0000']);
      // .range(['#66c2a5','#fc8d62','#8da0cb','#e78ac3','#a6d854','#ffd92f','#e5c494','#b3b3b3']);
      .range(['#1b9e77','#d95f02','#7570b3','#e7298a','#66a61e','#e6ab02','#a6761d','#666666']);
    const sortedStatsForHomerooms = _.sortBy(statsForHomerooms, ({grades, studentCount}) => {
      if (grades.length === 1) return (100 * orderedGrades.indexOf(grades[0])) - studentCount;
      if (grades.length > 1) return -1000 - studentCount;
      return -2000 - studentCount;
    });

    return (

          {this.renderTitle('Select a grade')}

            {orderedGrades.map((grade) => {
              return <button
                style={{
                  padding: 15,
                  paddingTop: 10,
                  paddingBottom: 10,
                  cursor: 'pointer',
                  margin: 3,
                  width: '5em',
                  border: '1px solid #ccc',
                  backgroundColor: (grade === gradeFilter) ? colors.selection : '#eee'
                }}
                key={grade}
                onClick={this.onGradeClicked.bind(this, grade)}>{grade}</button>;
            })}

This excludes mixed grade homerooms.

          {this.renderTitle('Compare across homeroom')}

                  Homeroom

                  Size

                  > 5 Absences

                  > 5 Tardies

                  > 0 Discipline

                  Disability

                  Limited English

                  Low income

                  Racial composition

                Students of color
                Hispanic

              {sortedStatsForHomerooms.map((statsForHomeroom) => {
                const {
                  homeroomName,
                  homeroomId,
                  studentCount,
                  raceGroups,
                  lunchPercent,
                  hispanicPercent,
                  disabilityPercent,
                  lepPercent,
                  colorPercent,
                  overOneWeekAbsentPercent,
                  overOneWeekTardyPercent,
                  hasDisciplinePercent,
                } = statsForHomeroom;
                const total = _.map(raceGroups, 'count').reduce((sum, a) => {
                  return sum + a;
                }, 0);

                return (

                      <a href={`/homerooms/${homeroomId}`}>{homeroomName}

                    {studentCount}

                    {/* Attendance / Discipline */}

                      <Bar percent={overOneWeekAbsentPercent} styles={styles.bar} threshold={10} />

                      <Bar percent={overOneWeekTardyPercent} styles={styles.bar} threshold={10} />

                      <Bar percent={hasDisciplinePercent} styles={styles.bar} threshold={10} />

                    {/* Demographics */}

                      <Bar percent={disabilityPercent} styles={styles.bar} threshold={10} />

                      <Bar percent={lepPercent} styles={styles.bar} threshold={10} />

                      <Bar percent={lunchPercent} styles={styles.bar} threshold={10} />

                    {/* Race */}

                        {_.sortBy(raceGroups, 'race').map((group) => {
                          const {race, count} = group;
                          const percent = 100 * count / total;
                          const title = Math.round(percent) + '% ' + race;
                          if (percent === 0) return null;

                          return (
                            <Bar
                              key={race}
                              percent={percent}
                              threshold={10}
                              title={title}
                              styles={{backgroundColor: color(race)}}
                              innerStyles={{color: 'white'}} />
                          );
                        })}

                      <Bar percent={colorPercent} styles={styles.bar} threshold={10} />

                      <Bar percent={hispanicPercent} styles={styles.bar} threshold={10} />

                );
              })}

          {this.renderTitle('Color key')}

            {allRaces.map((race) => {
              return
{race}
;
            })}

    );
  }

  renderTitle(title) {
    return
{title}
;
  }

}

Breakdown.propTypes = {
  students: React.PropTypes.arrayOf(React.PropTypes.shape({
    race: React.PropTypes.string,
    disability: React.PropTypes.string,
    limited_english_proficiency: React.PropTypes.string,
    gender: React.PropTypes.string,
    grade: React.PropTypes.string,
    free_reduced_lunch: React.PropTypes.string,
    hispanic_latino: React.PropTypes.bool,
    homeroom_id: React.PropTypes.number,
    homeroom_name: React.PropTypes.string
  })).isRequired
};
<|endoftext|>