<commit_before>angular.module('app.edit', [])
.controller('EditController', function($scope, $http, $location, $route, $routeParams, $rootScope, Snippets) {
  // a collection of highlighted ranges belonging to the current snippet
  $scope.ranges = [];
  $scope.rangeId = -1;
  $scope.colorCount = -1;

  // the ace editor instance
  var editor = ace.edit("editor");
  editor.setTheme("ace/theme/solarized_dark");
  editor.session.setMode("ace/mode/javascript");

  // use Ace's Range instead of the window's
  var Range = ace.require('ace/range').Range;

  // stop the browser's warning message
  editor.$blockScrolling = Infinity;

  // Adjust font size in editor when +/- button is clicked
  $scope.fontSize = 12;
  editor.setFontSize($scope.fontSize);

  $scope.enlargeText = function () {
    $scope.fontSize += 2;
    editor.setFontSize($scope.fontSize);
  };
  $scope.reduceText = function () {
    $scope.fontSize -= 2;
    editor.setFontSize($scope.fontSize);
  };

  // Remove annotations
  $scope.removeAnnotation = function() {
    // remove this range from the ranges collection
    var r = $scope.ranges.filter( function(range) {
      return range.id !== this.annotation.id;
    }.bind(this));
    $scope.ranges = r;
    editor.session.removeMarker(this.annotation.marker);

    // remove the annotation from the dom
    var el = document.getElementById('annotation-'+ this.annotation.id);
    el.parentNode.removeChild(el);
  };

  // Highlight selected text and create an annotation
  $scope.annotate = function() {

    // increment the colorCount and rangeId for next annotation
    $scope.colorCount++;
    if ($scope.colorCount === 4) {
      $scope.colorCount = 0;
    }
    $scope.rangeId++;

    // find the perimeter of the highlighted area
    var startRow = editor.session.selection.selectionLead.row;
    var startCol = editor.session.selection.selectionLead.column;
    var endRow = editor.session.selection.selectionAnchor.row;
    var endCol = editor.session.selection.selectionAnchor.column;

    // if no text was highlighted, do nothing
    if (startRow === endRow && startCol === endCol ) {
      return;
    }

    /* Mario: The start and end values depend on the direction we highlight the text
    * To highlight text, we need startRow < startCol and startCol < endCol.
    * To ensure that swap start and end values if necessary. */
    var temp;
    if ( startRow > endRow ) {
      temp = startRow;
      startRow = endRow;
      endRow = temp;
    }
    if ( startCol > endCol ) {
      temp = startCol;
      startCol = endCol;
      endCol = temp;
    }

    // create the new range object
    var range = new Range(startRow, startCol, endRow, endCol);

    // highlight the specified range in the editor
    var marker = editor.session.addMarker(range, "highlighter-" + $scope.colorCount, "text");

    // append pertinent info to this range object
    range.id = $scope.rangeId;
    range.color = $scope.colorCount;
    range.marker = marker;
    $scope.ranges.push(range);

    // TODO: focus cursor to new annotation
    return;
  };

  $scope.snippetsEdit = function() {
    var ranges = [];

    $scope.ranges.forEach(function(range) {
      // Using JSON.parse(JSON.stringify()) to break the reference.
      // Just performing a range.text assignment was breaking the innerText.
      ranges[range.id] = JSON.parse(JSON.stringify(range));
      ranges[range.id].text = document.getElementById('annotation-' + range.id).childNodes[1].innerText;
    });

    var tags = [];
    if ($scope.tags[0]) { tags.push($scope.tags[0]); }
    if ($scope.tags[1]) { tags.push($scope.tags[1]); }
    if ($scope.tags[1]) { tags.push($scope.tags[2]); }

    var snippet = {
      title: $scope.title,
      isPrivate: $scope.isPrivate,
      code: editor.getValue(),
      highlights: ranges,
      tags: tags
    };

    $http({
      method: 'PATCH',
      url: '/snippets/' + $routeParams.id,
      data: snippet
    })
    .then(function(res) {
      if (res.data.ok) {
        $location.path('/snippet/' + $routeParams.id);
      }
      $rootScope.successMsg = ['Successfully saved ' + $scope.title];
    }, function(err) {
      console.log('an error occurred in snippetsEdit');
    });
  };

  $scope.convertTime = function (mongoTime) {
    date = new Date(mongoTime);
    return (date.getMonth() + 1) + '.' + date.getDate() + '.' + date.getFullYear();
  }

  $scope.init = function() {
    Snippets.retrieveSnippet($routeParams.id)
    .then(function(snippet) {
      console.log(snippet);

      if (snippet !== false) {
        // set editor to display
        $scope.displayEditor = true;

        // set snippet data
        $scope.title = snippet.data.title;
        $scope.username = snippet.data.userName;
        $scope.createdAt = snippet.data.createdAt;
        $scope.tags = snippet.data.tags;

        // add code to editor
        editor.setValue(snippet.data.code);

        snippet.data.highlights.forEach(function(highlight) {
          // set markers for highlighted code
          var range = new Range(highlight.start.row, highlight.start.column, highlight.end.row, highlight.end.column);
          var marker = editor.session.addMarker(range, "highlighter-" + highlight.color);
          highlight.marker = marker; // overwrite stored marker data with new marker

          // store range data
          range.id = ++$scope.rangeId;
          range.color = highlight.color;
          range.marker = marker;
          range.text = highlight.text;
          $scope.ranges.push(range);
        });
      }
    });
  }();
})

// This directive allows two-way data binding in contenteditable div
.directive("contenteditable", function() {
  return {
    restrict: "A",
    require: "ngModel",
    link: function(scope, element, attrs, ngModel) {

      function read() {
        ngModel.$setViewValue(element.html());
      }

      ngModel.$render = function() {
        element.html(ngModel.$viewValue || "");
      };

      element.bind("blur keyup change", function() {
        scope.$apply(read);
      });
    }
  };
})
<commit_msg>Fix deletions of non-last annotations<commit_after>angular.module('app.edit', [])
.controller('EditController', function($scope, $http, $location, $route, $routeParams, $rootScope, Snippets) {
  // a collection of highlighted ranges belonging to the current snippet
  $scope.ranges = [];
  $scope.rangeId = -1;
  $scope.colorCount = -1;

  // the ace editor instance
  var editor = ace.edit("editor");
  editor.setTheme("ace/theme/solarized_dark");
  editor.session.setMode("ace/mode/javascript");

  // use Ace's Range instead of the window's
  var Range = ace.require('ace/range').Range;

  // stop the browser's warning message
  editor.$blockScrolling = Infinity;

  // Adjust font size in editor when +/- button is clicked
  $scope.fontSize = 12;
  editor.setFontSize($scope.fontSize);

  $scope.enlargeText = function () {
    $scope.fontSize += 2;
    editor.setFontSize($scope.fontSize);
  };
  $scope.reduceText = function () {
    $scope.fontSize -= 2;
    editor.setFontSize($scope.fontSize);
  };

  // Remove annotations
  $scope.removeAnnotation = function() {
    // remove this range from the ranges collection
    var r = $scope.ranges.filter( function(range) {
      return range.id !== this.annotation.id;
    }.bind(this));
    $scope.ranges = r;
    editor.session.removeMarker(this.annotation.marker);

    // remove the annotation from the dom
    var el = document.getElementById('annotation-'+ this.annotation.id);
    el.parentNode.removeChild(el);
  };

  // Highlight selected text and create an annotation
  $scope.annotate = function() {

    // increment the colorCount and rangeId for next annotation
    $scope.colorCount++;
    if ($scope.colorCount === 4) {
      $scope.colorCount = 0;
    }
    $scope.rangeId++;

    // find the perimeter of the highlighted area
    var startRow = editor.session.selection.selectionLead.row;
    var startCol = editor.session.selection.selectionLead.column;
    var endRow = editor.session.selection.selectionAnchor.row;
    var endCol = editor.session.selection.selectionAnchor.column;

    // if no text was highlighted, do nothing
    if (startRow === endRow && startCol === endCol ) {
      return;
    }

    /* Mario: The start and end values depend on the direction we highlight the text
    * To highlight text, we need startRow < startCol and startCol < endCol.
    * To ensure that swap start and end values if necessary. */
    var temp;
    if ( startRow > endRow ) {
      temp = startRow;
      startRow = endRow;
      endRow = temp;
    }
    if ( startCol > endCol ) {
      temp = startCol;
      startCol = endCol;
      endCol = temp;
    }

    // create the new range object
    var range = new Range(startRow, startCol, endRow, endCol);

    // highlight the specified range in the editor
    var marker = editor.session.addMarker(range, "highlighter-" + $scope.colorCount, "text");

    // append pertinent info to this range object
    range.id = $scope.rangeId;
    range.color = $scope.colorCount;
    range.marker = marker;
    $scope.ranges.push(range);

    // TODO: focus cursor to new annotation
    return;
  };

  $scope.snippetsEdit = function() {
    var ranges = [];

    $scope.ranges.forEach(function(range) {
      // Using JSON.parse(JSON.stringify()) to break the reference.
      // Just performing a range.text assignment was breaking the innerText.
      var tempRange;
      tempRange = JSON.parse(JSON.stringify(range));
      tempRange.text = document.getElementById('annotation-' + range.id).childNodes[1].innerText;
      ranges.push(tempRange);
    });

    var tags = [];
    if ($scope.tags[0]) { tags.push($scope.tags[0]); }
    if ($scope.tags[1]) { tags.push($scope.tags[1]); }
    if ($scope.tags[1]) { tags.push($scope.tags[2]); }

    var snippet = {
      title: $scope.title,
      isPrivate: $scope.isPrivate,
      code: editor.getValue(),
      highlights: ranges,
      tags: tags
    };

    $http({
      method: 'PATCH',
      url: '/snippets/' + $routeParams.id,
      data: snippet
    })
    .then(function(res) {
      if (res.data.ok) {
        $location.path('/snippet/' + $routeParams.id);
      }
      $rootScope.successMsg = ['Successfully saved ' + $scope.title];
    }, function(err) {
      console.log('an error occurred in snippetsEdit');
    });
  };

  $scope.convertTime = function (mongoTime) {
    date = new Date(mongoTime);
    return (date.getMonth() + 1) + '.' + date.getDate() + '.' + date.getFullYear();
  }

  $scope.init = function() {
    Snippets.retrieveSnippet($routeParams.id)
    .then(function(snippet) {
      console.log(snippet);

      if (snippet !== false) {
        // set editor to display
        $scope.displayEditor = true;

        // set snippet data
        $scope.title = snippet.data.title;
        $scope.username = snippet.data.userName;
        $scope.createdAt = snippet.data.createdAt;
        $scope.tags = snippet.data.tags;

        // add code to editor
        editor.setValue(snippet.data.code);

        snippet.data.highlights.forEach(function(highlight) {
          // set markers for highlighted code
          var range = new Range(highlight.start.row, highlight.start.column, highlight.end.row, highlight.end.column);
          var marker = editor.session.addMarker(range, "highlighter-" + highlight.color);
          highlight.marker = marker; // overwrite stored marker data with new marker

          // store range data
          range.id = ++$scope.rangeId;
          range.color = highlight.color;
          range.marker = marker;
          range.text = highlight.text;
          $scope.ranges.push(range);
        });
      }
    });
  }();
})

// This directive allows two-way data binding in contenteditable div
.directive("contenteditable", function() {
  return {
    restrict: "A",
    require: "ngModel",
    link: function(scope, element, attrs, ngModel) {

      function read() {
        ngModel.$setViewValue(element.html());
      }

      ngModel.$render = function() {
        element.html(ngModel.$viewValue || "");
      };

      element.bind("blur keyup change", function() {
        scope.$apply(read);
      });
    }
  };
})

{'fraction_non_alphanumeric': 0.13638959132066236, 'fraction_numerical': 0.002610327106615548, 'mean_word_length': 2.675059952038369, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3903854', 'n_tokens_mistral': 3729, 'n_tokens_neox': 3591, 'n_words': 895}