<commit_before>var SMALL_NUM = 0.001;

var EventEmitter = require('events').EventEmitter;
var assert = require('assert');
var fs = require('fs');

var BarStyleAttrs = (function() {
  var code = fs.readFileSync(__dirname + '/../src/styles/bar_style.js');
  code = '(function() {' + code + ';return BarStyleAttrs})()';
  return eval(code);
})();

var MorphingBarLandscape = (function() {
  var code = fs.readFileSync(__dirname + '/../src/styles/bar_morphing.js');
  code = '(function() {' + code + ';return MorphingBarLandscape})()';
  return eval(code);
})();

function testComputeRangeMorphingMiddle() {
  for (var morphingValue = 0; morphingValue <= 1; morphingValue += 0.1) {
    var attrs = new BarStyleAttrs({
      leftMargin: 17,
      rightMargin: 19,
      barSpacing: 7,
      barWidth: 13
    });

    var landscape = new MorphingBarLandscape({
      attrs: attrs,
      pointCount: 11,
      morphingIndex: 6,
      morphingVisibility: morphingValue
    });

    var morphingPadding = (0.5 + 0.5*morphingValue) * 7;
    var spaceBeforeMorphing = 17 + 6*13 + (5+0.5+0.5*morphingValue)*7;
    var spaceAfterMorphing = 19 + 4*13 + (3+0.5+0.5*morphingValue)*7;
    var morphingWidth = 13 * morphingValue;
    var totalWidth = spaceBeforeMorphing + morphingWidth + spaceAfterMorphing;

    // These are of the form [left, width, startIndex, length].
    var tests = [
      // Make sure it behaves regularly before the morphing bar.
      [0, 1, 0, 1],
      [1, 1, 0, 1],
      [0, 17+13, 0, 1],
      [0, 17+13+7-SMALL_NUM, 0, 1],
      [0, 17+13+7+SMALL_NUM, 0, 2],
      [1, 17+13+6-SMALL_NUM, 0, 1],
      [1, 17+13+6+SMALL_NUM, 0, 2],
      [17+13-SMALL_NUM, 1, 0, 1],
      [17+13+SMALL_NUM, 1, 1, 1],
      [17+13+SMALL_NUM, 1, 1, 1],
      [17+13+SMALL_NUM, 7+13, 1, 1],
      [17+13+SMALL_NUM, 7+13+7-SMALL_NUM*2, 1, 1],
      [17+13+SMALL_NUM, 7+13+7+SMALL_NUM*2, 1, 2],
      // Barely touch the left of the morphing bar.
      [0, spaceBeforeMorphing-SMALL_NUM, 0, 6],
      [0, spaceBeforeMorphing+SMALL_NUM, 0, 7],
      [19+13, spaceBeforeMorphing-13-19-SMALL_NUM, 1, 5],
      [19+13, spaceBeforeMorphing-13-19+SMALL_NUM, 1, 6],
      [spaceBeforeMorphing-8, 8-SMALL_NUM, 5, 1],
      [spaceBeforeMorphing-8, 8+SMALL_NUM, 5, 2],
      [spaceBeforeMorphing-SMALL_NUM*2, SMALL_NUM, 6, 1],
      // Barely touch the right of the morphing bar.
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, 1, 7, 1],
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, SMALL_NUM, 7, 1],
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, totalWidth, 7, 4],
      [spaceBeforeMorphing+morphingWidth-SMALL_NUM, 1, 6, 1],
      // Surround the morphing bar.
      [spaceBeforeMorphing-SMALL_NUM, morphingWidth+SMALL_NUM*2, 6, 1],
      [
        spaceBeforeMorphing - morphingPadding + SMALL_NUM,
        morphingPadding*2 + morphingWidth - SMALL_NUM*2,
        6,
        1
      ],
      // Make sure it behaves regularly after the morphing bar.
      [0, totalWidth, 0, 11],
      [0, totalWidth-1, 0, 11],
      [0, totalWidth*2, 0, 11],
      [0, totalWidth-19-13+SMALL_NUM, 0, 11],
      [0, totalWidth-19-13-SMALL_NUM, 0, 10],
      [0, totalWidth-19-14, 0, 10],
      [totalWidth-19-13-7+SMALL_NUM, 1, 10, 1],
      [totalWidth-19-13-8, 2, 9, 1],
      [totalWidth-19-13-8, 1-SMALL_NUM, 9, 1],
      [totalWidth-19-13-8, 8-SMALL_NUM, 9, 1],
      [totalWidth-19-13-8, 8+SMALL_NUM, 9, 2],
      [totalWidth-19-13-8, 9, 9, 2]
    ];

    for (var i = 0, len = tests.length; i < len; ++i) {
      var test = tests[i];
      var range = landscape.computeRange({left: test[0], width: test[1]});
      assert.equal(range.startIndex, test[2], test);
      assert.equal(range.length, test[3], test);
    }
  }
}

function testComputeRangeMorphingNearEnd() {
  for (var morphingValue = 0; morphingValue <= 1; morphingValue += 0.1) {
    var attrs = new BarStyleAttrs({
      leftMargin: 17,
      rightMargin: 19,
      barSpacing: 7,
      barWidth: 13
    });

    var landscape = new MorphingBarLandscape({
      attrs: attrs,
      pointCount: 11,
      morphingIndex: 9,
      morphingVisibility: morphingValue
    });

    var morphingPadding = (0.5 + 0.5*morphingValue) * 7;
    var spaceBeforeMorphing = 17 + 9*13 + (8+0.5+0.5*morphingValue)*7;
    var spaceAfterMorphing = 19 + 13 + morphingPadding;
    var morphingWidth = 13 * morphingValue;
    var totalWidth = spaceBeforeMorphing + morphingWidth + spaceAfterMorphing;

    // These are of the form [left, width, startIndex, length].
    var tests = [
      // Make sure it behaves regularly before the morphing bar.
      [0, 1, 0, 1],
      [1, 1, 0, 1],
      [0, 17+13, 0, 1],
      [0, 17+13+7-SMALL_NUM, 0, 1],
      [0, 17+13+7+SMALL_NUM, 0, 2],
      [1, 17+13+6-SMALL_NUM, 0, 1],
      [1, 17+13+6+SMALL_NUM, 0, 2],
      [17+13-SMALL_NUM, 1, 0, 1],
      [17+13+SMALL_NUM, 1, 1, 1],
      [17+13+SMALL_NUM, 1, 1, 1],
      [17+13+SMALL_NUM, 7+13, 1, 1],
      [17+13+SMALL_NUM, 7+13+7-SMALL_NUM*2, 1, 1],
      [17+13+SMALL_NUM, 7+13+7+SMALL_NUM*2, 1, 2],
      // Barely touch the left of the morphing bar.
      [0, spaceBeforeMorphing-SMALL_NUM, 0, 9],
      [0, spaceBeforeMorphing+SMALL_NUM, 0, 10],
      [19+13, spaceBeforeMorphing-13-19-SMALL_NUM, 1, 8],
      [19+13, spaceBeforeMorphing-13-19+SMALL_NUM, 1, 9],
      [spaceBeforeMorphing-8, 8-SMALL_NUM, 8, 1],
      [spaceBeforeMorphing-8, 8+SMALL_NUM, 8, 2],
      [spaceBeforeMorphing-SMALL_NUM*2, SMALL_NUM, 9, 1],
      // Barely touch the right of the morphing bar.
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, 1, 10, 1],
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, SMALL_NUM, 10, 1],
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, totalWidth, 10, 1],
      [spaceBeforeMorphing+morphingWidth-SMALL_NUM, 1, 9, 1],
      [totalWidth-19-13-morphingPadding-SMALL_NUM, totalWidth, 9, 2],
      // Surround the morphing bar.
      [spaceBeforeMorphing-SMALL_NUM, morphingWidth+SMALL_NUM*2, 9, 1],
      [
        spaceBeforeMorphing - morphingPadding + SMALL_NUM,
        morphingPadding*2 + morphingWidth - SMALL_NUM*2,
        9,
        1
      ],
      // Make sure it behaves regularly after the morphing bar.
      [0, totalWidth, 0, 11],
      [0, totalWidth-1, 0, 11],
      [0, totalWidth*2, 0, 11],
      [0, totalWidth-19-13+SMALL_NUM, 0, 11],
      [0, totalWidth-19-13-SMALL_NUM, 0, 10],
      [0, totalWidth-19-14, 0, 10],
      [totalWidth-19-13-morphingPadding+SMALL_NUM, totalWidth, 10, 1],
      [totalWidth-19-13-2, 1, 10, 1]
    ];

    for (var i = 0, len = tests.length; i < len; ++i) {
      var test = tests[i];
      var range = landscape.computeRange({left: test[0], width: test[1]});
      assert.equal(range.startIndex, test[2], test);
      assert.equal(range.length, test[3], test);
    }
  }
}

function testComputeRangeMorphingNearStart() {
  for (var morphingValue = 0; morphingValue <= 1; morphingValue += 0.1) {
    var attrs = new BarStyleAttrs({
      leftMargin: 17,
      rightMargin: 19,
      barSpacing: 7,
      barWidth: 13
    });

    var landscape = new MorphingBarLandscape({
      attrs: attrs,
      pointCount: 11,
      morphingIndex: 1,
      morphingVisibility: morphingValue
    });

    var morphingPadding = (0.5 + 0.5*morphingValue) * 7;
    var spaceBeforeMorphing = 17 + 13 + morphingPadding;
    var spaceAfterMorphing = 19 + 9*13 + (8+0.5+0.5*morphingValue)*7;
    var morphingWidth = 13 * morphingValue;
    var totalWidth = spaceBeforeMorphing + morphingWidth + spaceAfterMorphing;

    // These are of the form [left, width, startIndex, length].
    var tests = [
      // Make sure it behaves regularly before the morphing bar.
      [0, 1, 0, 1],
      [1, 1, 0, 1],
      [0, 17+13, 0, 1],
      [0, 17+13+morphingPadding-SMALL_NUM, 0, 1],
      [1, 17+12+morphingPadding-SMALL_NUM, 0, 1],
      [17+13-SMALL_NUM, 1, 0, 1],
      // Barely touch the left of the morphing bar.
      [17+13+SMALL_NUM, 1, 1, 1],
      [1, 17+13+morphingPadding, 0, 2],
      [0, spaceBeforeMorphing-SMALL_NUM, 0, 1],
      [0, spaceBeforeMorphing+SMALL_NUM, 0, 2],
      [spaceBeforeMorphing-8, 8-SMALL_NUM, 0, 1],
      [spaceBeforeMorphing-8, 8+SMALL_NUM, 0, 2],
      [spaceBeforeMorphing-SMALL_NUM*2, SMALL_NUM, 1, 1],
      // Barely touch the right of the morphing bar.
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, 1, 2, 1],
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, SMALL_NUM, 2, 1],
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, totalWidth, 2, 9],
      [spaceBeforeMorphing+morphingWidth-SMALL_NUM, 1, 1, 1],
      // Surround the morphing bar.
      [spaceBeforeMorphing-SMALL_NUM, morphingWidth+SMALL_NUM*2, 1, 1],
      [
        spaceBeforeMorphing - morphingPadding + SMALL_NUM,
        morphingPadding*2 + morphingWidth - SMALL_NUM*2,
        1,
        1
      ],
      // Make sure it behaves regularly after the morphing bar.
      [0, totalWidth, 0, 11],
      [0, totalWidth-1, 0, 11],
      [0, totalWidth*2, 0, 11],
      [0, totalWidth-19-13+SMALL_NUM, 0, 11],
      [0, totalWidth-19-13-SMALL_NUM, 0, 10],
      [0, totalWidth-19-14, 0, 10],
      [totalWidth-19-13-7+SMALL_NUM, 1, 10, 1],
      [totalWidth-19-13-8, 2, 9, 1],
      [totalWidth-19-13-8, 1-SMALL_NUM, 9, 1],
      [totalWidth-19-13-8, 8-SMALL_NUM, 9, 1],
      [totalWidth-19-13-8, 8+SMALL_NUM, 9, 2],
      [totalWidth-19-13-8, 9, 9, 2]
    ];

    for (var i = 0, len = tests.length; i < len; ++i) {
      var test = tests[i];
      var range = landscape.computeRange({left: test[0], width: test[1]});
      assert.equal(range.startIndex, test[2], test);
      assert.equal(range.length, test[3], test);
    }
  }
}

// TODO: make one method that does tests from other three morphing range tests.

// TODO: in the test for computeRegion, try giving it a completely hidden morphing bar and see what
// happens.

testComputeRangeMorphingMiddle();
testComputeRangeMorphingNearEnd();
testComputeRangeMorphingNearStart();
console.log('PASS');
<commit_msg>BarStyle: general computeRangeTests() for tests<commit_after>var SMALL_NUM = 0.001;

var EventEmitter = require('events').EventEmitter;
var assert = require('assert');
var fs = require('fs');

var BarStyleAttrs = (function() {
  var code = fs.readFileSync(__dirname + '/../src/styles/bar_style.js');
  code = '(function() {' + code + ';return BarStyleAttrs})()';
  return eval(code);
})();

var MorphingBarLandscape = (function() {
  var code = fs.readFileSync(__dirname + '/../src/styles/bar_morphing.js');
  code = '(function() {' + code + ';return MorphingBarLandscape})()';
  return eval(code);
})();

function computeRangeTests(morphingIndex, generator) {
  for (var v = 0; v <= 10; v += 10) {
    var attrs = new BarStyleAttrs({
      leftMargin: 17,
      rightMargin: 19,
      barSpacing: 7,
      barWidth: 13
    });
    var landscape = new MorphingBarLandscape({
      attrs: attrs,
      pointCount: 11,
      morphingIndex: morphingIndex,
      morphingVisibility: v / 10
    });

    var tests = generator(v / 10);
    for (var i = 0, len = tests.length; i < len; ++i) {
      var test = tests[i];
      var range = landscape.computeRange({left: test[0], width: test[1]});
      var msg = 'Expected ' + JSON.stringify({startIndex: test[2], length: test[3]}) +
        ' but got ' + JSON.stringify(range) + ' for test ' + i + ' and visibility=' + v/10;
      assert.equal(range.startIndex, test[2], msg);
      assert.equal(range.length, test[3], msg);
    }
  }
}

function testComputeRangeMorphingMiddle() {
  computeRangeTests(6, function(morphingValue) {
    var morphingPadding = (0.5 + 0.5*morphingValue) * 7;
    var spaceBeforeMorphing = 17 + 6*13 + (5+0.5+0.5*morphingValue)*7;
    var spaceAfterMorphing = 19 + 4*13 + (3+0.5+0.5*morphingValue)*7;
    var morphingWidth = 13 * morphingValue;
    var totalWidth = spaceBeforeMorphing + morphingWidth + spaceAfterMorphing;

    return [
      // Make sure it behaves regularly before the morphing bar.
      [0, 1, 0, 1],
      [1, 1, 0, 1],
      [0, 17+13, 0, 1],
      [0, 17+13+7-SMALL_NUM, 0, 1],
      [0, 17+13+7+SMALL_NUM, 0, 2],
      [1, 17+13+6-SMALL_NUM, 0, 1],
      [1, 17+13+6+SMALL_NUM, 0, 2],
      [17+13-SMALL_NUM, 1, 0, 1],
      [17+13+SMALL_NUM, 1, 1, 1],
      [17+13+SMALL_NUM, 1, 1, 1],
      [17+13+SMALL_NUM, 7+13, 1, 1],
      [17+13+SMALL_NUM, 7+13+7-SMALL_NUM*2, 1, 1],
      [17+13+SMALL_NUM, 7+13+7+SMALL_NUM*2, 1, 2],
      // Barely touch the left of the morphing bar.
      [0, spaceBeforeMorphing-SMALL_NUM, 0, 6],
      [0, spaceBeforeMorphing+SMALL_NUM, 0, 7],
      [19+13, spaceBeforeMorphing-13-19-SMALL_NUM, 1, 5],
      [19+13, spaceBeforeMorphing-13-19+SMALL_NUM, 1, 6],
      [spaceBeforeMorphing-8, 8-SMALL_NUM, 5, 1],
      [spaceBeforeMorphing-8, 8+SMALL_NUM, 5, 2],
      [spaceBeforeMorphing-SMALL_NUM*2, SMALL_NUM, 6, 1],
      // Barely touch the right of the morphing bar.
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, 1, 7, 1],
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, SMALL_NUM, 7, 1],
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, totalWidth, 7, 4],
      [spaceBeforeMorphing+morphingWidth-SMALL_NUM, 1, 6, 1],
      // Surround the morphing bar.
      [spaceBeforeMorphing-SMALL_NUM, morphingWidth+SMALL_NUM*2, 6, 1],
      [
        spaceBeforeMorphing - morphingPadding + SMALL_NUM,
        morphingPadding*2 + morphingWidth - SMALL_NUM*2,
        6,
        1
      ],
      // Make sure it behaves regularly after the morphing bar.
      [0, totalWidth, 0, 11],
      [0, totalWidth-1, 0, 11],
      [0, totalWidth*2, 0, 11],
      [0, totalWidth-19-13+SMALL_NUM, 0, 11],
      [0, totalWidth-19-13-SMALL_NUM, 0, 10],
      [0, totalWidth-19-14, 0, 10],
      [totalWidth-19-13-7+SMALL_NUM, 1, 10, 1],
      [totalWidth-19-13-8, 2, 9, 1],
      [totalWidth-19-13-8, 1-SMALL_NUM, 9, 1],
      [totalWidth-19-13-8, 8-SMALL_NUM, 9, 1],
      [totalWidth-19-13-8, 8+SMALL_NUM, 9, 2],
      [totalWidth-19-13-8, 9, 9, 2]
    ];
  });
}

function testComputeRangeMorphingNearEnd() {
  computeRangeTests(9, function(morphingValue) {
    var morphingPadding = (0.5 + 0.5*morphingValue) * 7;
    var spaceBeforeMorphing = 17 + 9*13 + (8+0.5+0.5*morphingValue)*7;
    var spaceAfterMorphing = 19 + 13 + morphingPadding;
    var morphingWidth = 13 * morphingValue;
    var totalWidth = spaceBeforeMorphing + morphingWidth + spaceAfterMorphing;

    return [
      // Make sure it behaves regularly before the morphing bar.
      [0, 1, 0, 1],
      [1, 1, 0, 1],
      [0, 17+13, 0, 1],
      [0, 17+13+7-SMALL_NUM, 0, 1],
      [0, 17+13+7+SMALL_NUM, 0, 2],
      [1, 17+13+6-SMALL_NUM, 0, 1],
      [1, 17+13+6+SMALL_NUM, 0, 2],
      [17+13-SMALL_NUM, 1, 0, 1],
      [17+13+SMALL_NUM, 1, 1, 1],
      [17+13+SMALL_NUM, 1, 1, 1],
      [17+13+SMALL_NUM, 7+13, 1, 1],
      [17+13+SMALL_NUM, 7+13+7-SMALL_NUM*2, 1, 1],
      [17+13+SMALL_NUM, 7+13+7+SMALL_NUM*2, 1, 2],
      // Barely touch the left of the morphing bar.
      [0, spaceBeforeMorphing-SMALL_NUM, 0, 9],
      [0, spaceBeforeMorphing+SMALL_NUM, 0, 10],
      [19+13, spaceBeforeMorphing-13-19-SMALL_NUM, 1, 8],
      [19+13, spaceBeforeMorphing-13-19+SMALL_NUM, 1, 9],
      [spaceBeforeMorphing-8, 8-SMALL_NUM, 8, 1],
      [spaceBeforeMorphing-8, 8+SMALL_NUM, 8, 2],
      [spaceBeforeMorphing-SMALL_NUM*2, SMALL_NUM, 9, 1],
      // Barely touch the right of the morphing bar.
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, 1, 10, 1],
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, SMALL_NUM, 10, 1],
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, totalWidth, 10, 1],
      [spaceBeforeMorphing+morphingWidth-SMALL_NUM, 1, 9, 1],
      [totalWidth-19-13-morphingPadding-SMALL_NUM, totalWidth, 9, 2],
      // Surround the morphing bar.
      [spaceBeforeMorphing-SMALL_NUM, morphingWidth+SMALL_NUM*2, 9, 1],
      [
        spaceBeforeMorphing - morphingPadding + SMALL_NUM,
        morphingPadding*2 + morphingWidth - SMALL_NUM*2,
        9,
        1
      ],
      // Make sure it behaves regularly after the morphing bar.
      [0, totalWidth, 0, 11],
      [0, totalWidth-1, 0, 11],
      [0, totalWidth*2, 0, 11],
      [0, totalWidth-19-13+SMALL_NUM, 0, 11],
      [0, totalWidth-19-13-SMALL_NUM, 0, 10],
      [0, totalWidth-19-14, 0, 10],
      [totalWidth-19-13-morphingPadding+SMALL_NUM, totalWidth, 10, 1],
      [totalWidth-19-13-2, 1, 10, 1]
    ];
  });
}

function testComputeRangeMorphingNearStart() {
  computeRangeTests(1, function(morphingValue) {
    var morphingPadding = (0.5 + 0.5*morphingValue) * 7;
    var spaceBeforeMorphing = 17 + 13 + morphingPadding;
    var spaceAfterMorphing = 19 + 9*13 + (8+0.5+0.5*morphingValue)*7;
    var morphingWidth = 13 * morphingValue;
    var totalWidth = spaceBeforeMorphing + morphingWidth + spaceAfterMorphing;

    return [
      // Make sure it behaves regularly before the morphing bar.
      [0, 1, 0, 1],
      [1, 1, 0, 1],
      [0, 17+13, 0, 1],
      [0, 17+13+morphingPadding-SMALL_NUM, 0, 1],
      [1, 17+12+morphingPadding-SMALL_NUM, 0, 1],
      [17+13-SMALL_NUM, 1, 0, 1],
      // Barely touch the left of the morphing bar.
      [17+13+SMALL_NUM, 1, 1, 1],
      [1, 17+13+morphingPadding, 0, 2],
      [0, spaceBeforeMorphing-SMALL_NUM, 0, 1],
      [0, spaceBeforeMorphing+SMALL_NUM, 0, 2],
      [spaceBeforeMorphing-8, 8-SMALL_NUM, 0, 1],
      [spaceBeforeMorphing-8, 8+SMALL_NUM, 0, 2],
      [spaceBeforeMorphing-SMALL_NUM*2, SMALL_NUM, 1, 1],
      // Barely touch the right of the morphing bar.
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, 1, 2, 1],
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, SMALL_NUM, 2, 1],
      [spaceBeforeMorphing+morphingWidth+SMALL_NUM, totalWidth, 2, 9],
      [spaceBeforeMorphing+morphingWidth-SMALL_NUM, 1, 1, 1],
      // Surround the morphing bar.
      [spaceBeforeMorphing-SMALL_NUM, morphingWidth+SMALL_NUM*2, 1, 1],
      [
        spaceBeforeMorphing - morphingPadding + SMALL_NUM,
        morphingPadding*2 + morphingWidth - SMALL_NUM*2,
        1,
        1
      ],
      // Make sure it behaves regularly after the morphing bar.
      [0, totalWidth, 0, 11],
      [0, totalWidth-1, 0, 11],
      [0, totalWidth*2, 0, 11],
      [0, totalWidth-19-13+SMALL_NUM, 0, 11],
      [0, totalWidth-19-13-SMALL_NUM, 0, 10],
      [0, totalWidth-19-14, 0, 10],
      [totalWidth-19-13-7+SMALL_NUM, 1, 10, 1],
      [totalWidth-19-13-8, 2, 9, 1],
      [totalWidth-19-13-8, 1-SMALL_NUM, 9, 1],
      [totalWidth-19-13-8, 8-SMALL_NUM, 9, 1],
      [totalWidth-19-13-8, 8+SMALL_NUM, 9, 2],
      [totalWidth-19-13-8, 9, 9, 2]
    ];
  });
}

// TODO: in the test for computeRegion, try giving it a completely hidden morphing bar and see what
// happens.

testComputeRangeMorphingMiddle();
testComputeRangeMorphingNearEnd();
testComputeRangeMorphingNearStart();
console.log('PASS');
<|endoftext|>


<commit_before>import Em from 'ember';
import defaultFor from 'ember-modals/utils/default-for';

export default Em.ObjectController.extend(
  Em.Evented, {

  /* Options - best set by extending this controller */

  defaultView: 'modal',
  defaultOutlet: 'modal',
  defaultParentView: 'application',
  transitionDuration: 500, // Fallback from CSS value

  /* Properties - best set using showModal() */

  controller: null,
  model: null,
  template: null,
  view: null,

  _outletBeingClosed: null,

  /**
  Triggers the animation to hide the modal. Once the `transitionDuration` has passed the action to remove the outlet will be triggered.

  If the outlet is not provided, the `defaultOutlet` property will be used. Whe using a modal other than the default you should always pass the outlet name.

  @method hide
  @param [outlet] String The name of the outlet to remove the modal from. Defaults to defaultOutlet
  */

  hide: function(outlet) {
    var parentView;

    outlet = defaultFor(outlet, this.get('defaultOutlet'));

    this.set('_outletBeingClosed', outlet);
    this.trigger('closeModal'); // Start close animation

    parentView = this.get('_previousRelationships.' + outlet);

    Em.run.later(this, function() {
      this.set('_outletBeingClosed', null);
      this.send('removeModal', outlet, parentView);
    }, this.get('transitionDuration'));
  },

  /**
  Renders a modal in a given outlet within the application using the properties previously set on this controller from another controller (either directly using set() or indirectly using the showMethod() method).

  If `outlet` or `parentView` are not set as a property on `renderingOptions`, `defaultOutlet` and `defaultParentView` will be used, respectively.

  @method show
  @param [renderingOptions] Object The `outlet` and `parentView` to render the modal into.
  */

  show: function(renderingOptions) {
    var previousRelationships = this.get('_previousRelationships');
    var options, previousParentView;

    renderingOptions = defaultFor(renderingOptions, {});

    options = {
      controller: this.get('controller'),
      template: this.get('template'),
      outlet: defaultFor(
        renderingOptions.outlet,
        this.get('defaultOutlet')
      ),
      into: defaultFor(
        renderingOptions.parentView,
        this.get('defaultParentView')
      ),
      view: defaultFor(
        this.get('view'),
        this.get('defaultView')
      )
    };

    /* Save the developer-defined coupling of outlet and
    parent view so we can easily hide outlets in a route's
    view later */

    previousParentView = previousRelationships.get(options.outlet);

    if (previousParentView && previousParentView !== options.into) {
      Em.warn('You should not use the same named outlet in different views with ember-modals. Behaviour will be unexpected.');
    }

    previousRelationships.set(options.outlet, options.into);

    this.send('renderModal', options);
  },

  _previousRelationships: Em.Object.create(),

});
<commit_msg>Fix for #17 - ObjectController --> Controller<commit_after>import Em from 'ember';
import defaultFor from 'ember-modals/utils/default-for';

export default Em.Controller.extend(
  Em.Evented, {

  /* Options - best set by extending this controller */

  defaultView: 'modal',
  defaultOutlet: 'modal',
  defaultParentView: 'application',
  transitionDuration: 500, // Fallback from CSS value

  /* Properties - best set using showModal() */

  controller: null,
  model: null,
  template: null,
  view: null,

  _outletBeingClosed: null,

  /**
  Triggers the animation to hide the modal. Once the `transitionDuration` has passed the action to remove the outlet will be triggered.

  If the outlet is not provided, the `defaultOutlet` property will be used. Whe using a modal other than the default you should always pass the outlet name.

  @method hide
  @param [outlet] String The name of the outlet to remove the modal from. Defaults to defaultOutlet
  */

  hide: function(outlet) {
    var parentView;

    outlet = defaultFor(outlet, this.get('defaultOutlet'));

    this.set('_outletBeingClosed', outlet);
    this.trigger('closeModal'); // Start close animation

    parentView = this.get('_previousRelationships.' + outlet);

    Em.run.later(this, function() {
      this.set('_outletBeingClosed', null);
      this.send('removeModal', outlet, parentView);
    }, this.get('transitionDuration'));
  },

  /**
  Renders a modal in a given outlet within the application using the properties previously set on this controller from another controller (either directly using set() or indirectly using the showMethod() method).

  If `outlet` or `parentView` are not set as a property on `renderingOptions`, `defaultOutlet` and `defaultParentView` will be used, respectively.

  @method show
  @param [renderingOptions] Object The `outlet` and `parentView` to render the modal into.
  */

  show: function(renderingOptions) {
    var previousRelationships = this.get('_previousRelationships');
    var options, previousParentView;

    renderingOptions = defaultFor(renderingOptions, {});

    options = {
      controller: this.get('controller'),
      template: this.get('template'),
      outlet: defaultFor(
        renderingOptions.outlet,
        this.get('defaultOutlet')
      ),
      into: defaultFor(
        renderingOptions.parentView,
        this.get('defaultParentView')
      ),
      view: defaultFor(
        this.get('view'),
        this.get('defaultView')
      )
    };

    /* Save the developer-defined coupling of outlet and
    parent view so we can easily hide outlets in a route's
    view later */

    previousParentView = previousRelationships.get(options.outlet);

    if (previousParentView && previousParentView !== options.into) {
      Em.warn('You should not use the same named outlet in different views with ember-modals. Behaviour will be unexpected.');
    }

    previousRelationships.set(options.outlet, options.into);

    this.send('renderModal', options);
  },

  _previousRelationships: Em.Object.create(),

});
<|endoftext|>


<commit_before>jQuery(function($) {
    var move = null;
    var months = 12;
    output_calendar(move, months);
    // 横移動
    $('#move_controls button').click(function () {
        var months = 12;
        months = $('#period_controls button.active').data('months');
        var move = null;
        var move_now = $('#move_now').data('now');
        if ($(this).hasClass('prev')){
            $('#move_now').data('now', move_now - 1);
        } else if ($(this).hasClass('next')){
            $('#move_now').data('now', move_now + 1);
        }
        move = $('#move_now').data('now');
        $('#move_controls button.active').removeClass('active');
        $(this).addClass('active');
        output_calendar(move, months);
    });
    // 表示期間
    $('#period_controls button').click(function () {
        var move = null;
        var months = 12;
        if ($('#move_controls button.active').data('move')){
            move = $('#move_controls button.active').data('move');
        }
        months = $(this).data('months');
        $('#period_controls button.active').removeClass('active');
        $(this).addClass('active');
        output_calendar(move, months);
    });
    // Add Money Data
    $("#Add_Money_Submit").click(function(){
        $.ajax({
            url: "/money/1/?action=ajax_add_money",
            type: "POST",
            data: $("form#Add_Money_Form").serialize(),
            success: function(json){
                var move = $('#move_now').data('now');
                var months = 12;
                months = $('#period_controls button.active').data('months');
                output_calendar(move, months);
                notif({
                    msg: "入出金を追加しました",
                    type: "info",
                    position: "center"
                });
            },
            error: function(){
                notif({
                    msg: "入出金の追加に失敗しました",
                    type: "info",
                    position: "center"
                });
            }
        });
        $('#Add_Money').modal('hide')
    });
    // Update Money Data
    $("#Update_Money_Submit").click(function(){
        $.ajax({
            url: "/money/1/?action=ajax_update_money",
            type: "POST",
            data: $("form#Update_Money_Form").serialize(),
            success: function(json){
                var move = $('#move_now').data('now');
                var months = 12;
                months = $('#period_controls button.active').data('months');
                output_calendar(move, months);
                notif({
                    msg: "入出金を更新しました",
                    type: "info",
                    position: "center"
                });
            },
            error: function(){
                notif({
                    msg: "入出金の更新に失敗しました",
                    type: "info",
                    position: "center"
                });
            }
        });
        $('#Update_Money').modal('hide')
    });
    // Delete Money Data
    $("#Delete_Money_Submit").click(function(){
        $.ajax({
            url: "/money/1/?action=ajax_delete_money",
            type: "POST",
            data: $("form#Delete_Money_Form").serialize(),
            success: function(json){
                var move = $('#move_now').data('now');
                var months = 12;
                months = $('#period_controls button.active').data('months');
                output_calendar(move, months);
                notif({
                    msg: "入出金を削除しました",
                    type: "info",
                    position: "center"
                });
            },
            error: function(){
                notif({
                    msg: "入出金の削除に失敗しました",
                    type: "info",
                    position: "center"
                });
            }
        });
        $('#Delete_Money').modal('hide')
    });
    $("select#AddSelectItem").change(function () {
        var jsonurl = '/ajax/get_item/' + $(this).val() + '/';
        $.getJSON(jsonurl,function(json){
            if (json.default_ammount){
                $('input#AddInputAmmount').val(json.default_ammount);
            }
        });
    });
    $("select#SelectItem").change(function () {
        var jsonurl = '/ajax/get_item/' + $(this).val() + '/';
        $.getJSON(jsonurl,function(json){
            if (json.default_ammount){
                $('input#InputAmmount').val(json.default_ammount);
            }
        });
    });
});
function output_calendar(move, months){
    $('#moneys').empty();
    $('.year').empty();
    $('#moneys').append('

');
    var start_date_year = moment().year();
    if (months) {
        period_months = months;
        if (months == 1){
            start_date_month = moment(moment().add(1, 'months')).month();
        } else if (months == 3){
            start_date_month = moment().month();
        } else if (months == 12){
            start_date_month = 1;
        } else {
            start_date_month = 1;
        }
    } else {
        start_date_month = 1;
        period_months = 12;
    }
    var start_date = String(start_date_year) + '-' + String(start_date_month) + '-1';
    moment_start_date = moment(start_date, "YYYY-MM-DD");
    if (move != null){
        moment_start_date = moment(moment_start_date).add(move*period_months, 'months')
    } else {
        moment_start_date = moment(moment_start_date)
    }
    $('.year').append(moment_start_date.year() + '年');
    for (var i=0; i<period_months; i++) {
        $('#moneys thead tr').append('' + String(moment(moment_start_date).add(i, 'months').month() + 1) + '月	');
        $('#moneys tfoot tr').append('' + String(moment(moment_start_date).add(i, 'months').month() + 1) + '月	');
    }
    for (var i=0; i<31; i++) {
        $('#moneys tbody').append('
' + String(i+1) + '日
');
        for (var j=0; j<period_months; j++) {
            if (moment(String(moment_start_date.year()) + '-' + String(moment(moment_start_date).month()+j+1) + '-' + String(i+1), "YYYY-MM-DD").isValid() == true){
                var cellclass = '';
                if (moment(String(moment_start_date.year()) + '-' + String(moment(moment_start_date).month()+j+1) + '-' + String(i+1)).format('YYYY-MM') == moment().format('YYYY-MM') || moment(String(moment_start_date.year()) + '-' + String(moment(moment_start_date).month()+j+1) + '-' + String(i+1)).format('YYYY-DD') == moment().format('YYYY-DD')){
                    cellclass = ' bg-info';
                }
                $('#day_row_' + String(i+1)).append('
<button class="btn btn-sm" data-toggle="modal" data-target="#Add_Money" data-date="' + String(moment(String(moment_start_date.year()) + '-' + String(moment(moment_start_date).month()+j+1) + '-' + String(i+1)).format('YYYY-MM-DD')) + '"></button>
	');
            } else {
                $('#day_row_' + String(i+1)).append('	');
            }
        }
    }
    get_money(moment_start_date.year(), moment_start_date.month()+1, period_months);
}
function get_money(year, month, period_months){
    var get_money_url = '/ajax/get_moneys/' + 1 + '/' + year + '/' + month + '/1/' + period_months + '/';
    $.getJSON(get_money_url, {
        start:$.now()
    },function(json){
        $.each(json, function(i, data){
            if (data.date){
                var ammountclass = '';
                var ammounticon = '';
                var balanceclass = '';
                var summarybr = '';
                if (data.ammount < 0) {
                    ammountclass = 'text-danger';
                }
                if (data.balance < 0) {
                    balanceclass = 'text-danger';
                }
                if (data.summary) {
                    summarybr = '
';
                }
                var ammount = separate(data.ammount);
                var balance = separate(data.balance);
                if (!$('.cell_child_' + data.date)[0]){
                $('.cell_' + data.date).append('

');
                }
                $('.cell_child_' + data.date).append('
' + ammount + '	' + data.item + summarybr + data.summary + '	' + balance + '
');
                /*
                $('.cell_' + data.date).append('
' + data.ammount + summarybr + data.summary + '' + data.item + '' + data.balance + '
');
                */
            }
        })
    });
}
function separate(num){
    return String(num).replace( /(\d)(?=(\d\d\d)+(?!\d))/g, '$1,');
}
jQuery(function($) {
    $('#Add_Money').on('show.bs.modal', function (event) {
        var button = $(event.relatedTarget)
        var date = button.data('date')
        var modal = $(this)
        modal.find('.modal-body input#AddInputDate').val(date)
    });
    $('#Update_Money').on('show.bs.modal', function (event) {
        var button = $(event.relatedTarget)
        var momey_id = button.data('momey_id')
        var modal = $(this)
        var get_itemcategory_url = '/ajax/get_money/' + momey_id + '/';
        $.getJSON(get_itemcategory_url, {
            start:$.now()
        },function(json){
            modal.find('.modal-body input#EditMoneyID').val(json.money_id)
            modal.find('.modal-body input#DeleteMoneyID').val(json.money_id)
            modal.find('.modal-body input#InputDate').val(json.date)
            modal.find('.modal-body select#SelectItem').val(json.item_id)
            modal.find('.modal-body input#InputAmmount').val(json.ammount)
            modal.find('.modal-body input#InputSummary').val(json.summary)
        });
    });
});<commit_msg>Update ajax-event-calendar.js<commit_after>jQuery(function($) {
    var move = null;
    var months = 12;
    output_calendar(move, months);
    // 横移動
    $('#move_controls button').click(function () {
        var months = 12;
        months = $('#period_controls button.active').data('months');
        var move = null;
        var move_now = $('#move_now').data('now');
        if ($(this).hasClass('prev')){
            $('#move_now').data('now', move_now - 1);
        } else if ($(this).hasClass('next')){
            $('#move_now').data('now', move_now + 1);
        }
        move = $('#move_now').data('now');
        $('#move_controls button.active').removeClass('active');
        $(this).addClass('active');
        output_calendar(move, months);
    });
    // 表示期間
    $('#period_controls button').click(function () {
        var move = null;
        var months = 12;
        if ($('#move_controls button.active').data('move')){
            move = $('#move_controls button.active').data('move');
        }
        months = $(this).data('months');
        $('#period_controls button.active').removeClass('active');
        $(this).addClass('active');
        output_calendar(move, months);
    });
    // Add Money Data
    $("#Add_Money_Submit").click(function(){
        var event_id = 1;
        $.ajax({
            url: "/money/" + event_id + "/?action=ajax_add_money",
            type: "POST",
            data: $("form#Add_Money_Form").serialize(),
            success: function(json){
                var move = $('#move_now').data('now');
                var months = 12;
                months = $('#period_controls button.active').data('months');
                output_calendar(move, months);
                notif({
                    msg: "入出金を追加しました",
                    type: "info",
                    position: "center"
                });
            },
            error: function(){
                notif({
                    msg: "入出金の追加に失敗しました",
                    type: "info",
                    position: "center"
                });
            }
        });
        $('#Add_Money').modal('hide')
    });
    // Update Money Data
    $("#Update_Money_Submit").click(function(){
        var event_id = 1;
        $.ajax({
            url: "/money/" + event_id + "/?action=ajax_update_money",
            type: "POST",
            data: $("form#Update_Money_Form").serialize(),
            success: function(json){
                var move = $('#move_now').data('now');
                var months = 12;
                months = $('#period_controls button.active').data('months');
                output_calendar(move, months);
                notif({
                    msg: "入出金を更新しました",
                    type: "info",
                    position: "center"
                });
            },
            error: function(){
                notif({
                    msg: "入出金の更新に失敗しました",
                    type: "info",
                    position: "center"
                });
            }
        });
        $('#Update_Money').modal('hide')
    });
    // Delete Money Data
    $("#Delete_Money_Submit").click(function(){
        var event_id = 1;
        $.ajax({
            url: "/money/" + event_id + "/?action=ajax_delete_money",
            type: "POST",
            data: $("form#Delete_Money_Form").serialize(),
            success: function(json){
                var move = $('#move_now').data('now');
                var months = 12;
                months = $('#period_controls button.active').data('months');
                output_calendar(move, months);
                notif({
                    msg: "入出金を削除しました",
                    type: "info",
                    position: "center"
                });
            },
            error: function(){
                notif({
                    msg: "入出金の削除に失敗しました",
                    type: "info",
                    position: "center"
                });
            }
        });
        $('#Delete_Money').modal('hide')
    });
    $("select#AddSelectItem").change(function () {
        var jsonurl = '/ajax/get_item/' + $(this).val() + '/';
        $.getJSON(jsonurl,function(json){
            if (json.default_ammount){
                $('input#AddInputAmmount').val(json.default_ammount);
            }
        });
    });
    $("select#SelectItem").change(function () {
        var jsonurl = '/ajax/get_item/' + $(this).val() + '/';
        $.getJSON(jsonurl,function(json){
            if (json.default_ammount){
                $('input#InputAmmount').val(json.default_ammount);
            }
        });
    });
});
function output_calendar(move, months){
    $('#moneys').empty();
    $('.year').empty();
    $('#moneys').append('

');
    var start_date_year = moment().year();
    if (months) {
        period_months = months;
        if (months == 1){
            start_date_month = moment(moment().add(1, 'months')).month();
        } else if (months == 3){
            start_date_month = moment().month();
        } else if (months == 12){
            start_date_month = 1;
        } else {
            start_date_month = 1;
        }
    } else {
        start_date_month = 1;
        period_months = 12;
    }
    var start_date = String(start_date_year) + '-' + String(start_date_month) + '-1';
    moment_start_date = moment(start_date, "YYYY-MM-DD");
    if (move != null){
        moment_start_date = moment(moment_start_date).add(move*period_months, 'months')
    } else {
        moment_start_date = moment(moment_start_date)
    }
    $('.year').append(moment_start_date.year() + '年');
    for (var i=0; i<period_months; i++) {
        $('#moneys thead tr').append('' + String(moment(moment_start_date).add(i, 'months').month() + 1) + '月	');
        $('#moneys tfoot tr').append('' + String(moment(moment_start_date).add(i, 'months').month() + 1) + '月	');
    }
    for (var i=0; i<31; i++) {
        $('#moneys tbody').append('
' + String(i+1) + '日
');
        for (var j=0; j<period_months; j++) {
            if (moment(String(moment_start_date.year()) + '-' + String(moment(moment_start_date).month()+j+1) + '-' + String(i+1), "YYYY-MM-DD").isValid() == true){
                var cellclass = '';
                if (moment(String(moment_start_date.year()) + '-' + String(moment(moment_start_date).month()+j+1) + '-' + String(i+1)).format('YYYY-MM') == moment().format('YYYY-MM') || moment(String(moment_start_date.year()) + '-' + String(moment(moment_start_date).month()+j+1) + '-' + String(i+1)).format('YYYY-DD') == moment().format('YYYY-DD')){
                    cellclass = ' bg-info';
                }
                $('#day_row_' + String(i+1)).append('
<button class="btn btn-sm" data-toggle="modal" data-target="#Add_Money" data-date="' + String(moment(String(moment_start_date.year()) + '-' + String(moment(moment_start_date).month()+j+1) + '-' + String(i+1)).format('YYYY-MM-DD')) + '"></button>
	');
            } else {
                $('#day_row_' + String(i+1)).append('	');
            }
        }
    }
    get_money(moment_start_date.year(), moment_start_date.month()+1, period_months);
}
function get_money(year, month, period_months){
    var get_money_url = '/ajax/get_moneys/' + 1 + '/' + year + '/' + month + '/1/' + period_months + '/';
    $.getJSON(get_money_url, {
        start:$.now()
    },function(json){
        $.each(json, function(i, data){
            if (data.date){
                var ammountclass = '';
                var ammounticon = '';
                var balanceclass = '';
                var summarybr = '';
                if (data.ammount < 0) {
                    ammountclass = 'text-danger';
                }
                if (data.balance < 0) {
                    balanceclass = 'text-danger';
                }
                if (data.summary) {
                    summarybr = '
';
                }
                var ammount = separate(data.ammount);
                var balance = separate(data.balance);
                if (!$('.cell_child_' + data.date)[0]){
                $('.cell_' + data.date).append('

');
                }
                $('.cell_child_' + data.date).append('
' + ammount + '	' + data.item + summarybr + data.summary + '	' + balance + '
');
                /*
                $('.cell_' + data.date).append('
' + data.ammount + summarybr + data.summary + '' + data.item + '' + data.balance + '
');
                */
            }
        })
    });
}
function separate(num){
    return String(num).replace( /(\d)(?=(\d\d\d)+(?!\d))/g, '$1,');
}
jQuery(function($) {
    $('#Add_Money').on('show.bs.modal', function (event) {
        var button = $(event.relatedTarget)
        var date = button.data('date')
        var modal = $(this)
        modal.find('.modal-body input#AddInputDate').val(date)
    });
    $('#Update_Money').on('show.bs.modal', function (event) {
        var button = $(event.relatedTarget)
        var momey_id = button.data('momey_id')
        var modal = $(this)
        var get_itemcategory_url = '/ajax/get_money/' + momey_id + '/';
        $.getJSON(get_itemcategory_url, {
            start:$.now()
        },function(json){
            modal.find('.modal-body input#EditMoneyID').val(json.money_id)
            modal.find('.modal-body input#DeleteMoneyID').val(json.money_id)
            modal.find('.modal-body input#InputDate').val(json.date)
            modal.find('.modal-body select#SelectItem').val(json.item_id)
            modal.find('.modal-body input#InputAmmount').val(json.ammount)
            modal.find('.modal-body input#InputSummary').val(json.summary)
        });
    });
});
<|endoftext|>


<commit_before>import Users from "meteor/vulcan:users";
import moment from 'moment';
import { ensureIndex } from '../../collectionUtils';

// Auto-generated indexes from production
ensureIndex(Users, {username:1}, {unique:true,sparse:1});
ensureIndex(Users, {"emails.address":1}, {unique:true,sparse:1});
ensureIndex(Users, {"services.resume.loginTokens.hashedToken":1}, {unique:true,sparse:1});
ensureIndex(Users, {"services.resume.loginTokens.token":1}, {unique:true,sparse:1});
ensureIndex(Users, {"services.resume.haveLoginTokensToDelete":1}, {sparse:1});
ensureIndex(Users, {"services.resume.loginTokens.when":1}, {sparse:1});
ensureIndex(Users, {"services.email.verificationTokens.token":1}, {unique:true,sparse:1});
ensureIndex(Users, {"services.password.reset.token":1}, {unique:true,sparse:1});
ensureIndex(Users, {"services.password.reset.when":1}, {sparse:1});
ensureIndex(Users, {"services.twitter.id":1}, {unique:true,sparse:1});
ensureIndex(Users, {"services.facebook.id":1}, {unique:true,sparse:1});
ensureIndex(Users, {"services.google.id":1}, {unique:true,sparse:1});
ensureIndex(Users, {karma:-1,_id:-1});
ensureIndex(Users, {slug:1});
ensureIndex(Users, {isAdmin:1});
ensureIndex(Users, {"services.github.id":1}, {unique:true,sparse:1});
ensureIndex(Users, {createdAt:-1,_id:-1});

Users.addView('LWSunshinesList', function(terms) {
  return {
    selector: {groups:'sunshineRegiment'},
    options: {
      sort: terms.sort
    }
  }
});

Users.addView('LWTrustLevel1List', function(terms) {
  return {
    selector: {groups:'trustLevel1'},
    options: {
      sort: terms.sort
    }
  }
});

Users.addView('LWUsersAdmin', terms => ({
  options: {
    sort: terms.sort
  }
}));

Users.addView("usersWithBannedUsers", function () {
  return {
    selector: {
      bannedUserIds: {$exists: true}
    },
  }
})

Users.addView("sunshineNewUsers", function () {
  const twoDaysAgo = moment().subtract(2, 'days').toDate();
  return {
    selector: {
      createdAt: {$gt: twoDaysAgo},
      $or: [
        { voteCount: {$gt: 0}},
        { commentCount: {$gt: 0}},
        { postCount: {$gt: 0}},
      ],
      reviewedByUserId: {$exists: false},
      banned: {$exists: false},
    },
  }
})
<commit_msg>Remove recency constraints on sunshine user review<commit_after>import Users from "meteor/vulcan:users";
import moment from 'moment';
import { ensureIndex } from '../../collectionUtils';

// Auto-generated indexes from production
ensureIndex(Users, {username:1}, {unique:true,sparse:1});
ensureIndex(Users, {"emails.address":1}, {unique:true,sparse:1});
ensureIndex(Users, {"services.resume.loginTokens.hashedToken":1}, {unique:true,sparse:1});
ensureIndex(Users, {"services.resume.loginTokens.token":1}, {unique:true,sparse:1});
ensureIndex(Users, {"services.resume.haveLoginTokensToDelete":1}, {sparse:1});
ensureIndex(Users, {"services.resume.loginTokens.when":1}, {sparse:1});
ensureIndex(Users, {"services.email.verificationTokens.token":1}, {unique:true,sparse:1});
ensureIndex(Users, {"services.password.reset.token":1}, {unique:true,sparse:1});
ensureIndex(Users, {"services.password.reset.when":1}, {sparse:1});
ensureIndex(Users, {"services.twitter.id":1}, {unique:true,sparse:1});
ensureIndex(Users, {"services.facebook.id":1}, {unique:true,sparse:1});
ensureIndex(Users, {"services.google.id":1}, {unique:true,sparse:1});
ensureIndex(Users, {karma:-1,_id:-1});
ensureIndex(Users, {slug:1});
ensureIndex(Users, {isAdmin:1});
ensureIndex(Users, {"services.github.id":1}, {unique:true,sparse:1});
ensureIndex(Users, {createdAt:-1,_id:-1});

Users.addView('LWSunshinesList', function(terms) {
  return {
    selector: {groups:'sunshineRegiment'},
    options: {
      sort: terms.sort
    }
  }
});

Users.addView('LWTrustLevel1List', function(terms) {
  return {
    selector: {groups:'trustLevel1'},
    options: {
      sort: terms.sort
    }
  }
});

Users.addView('LWUsersAdmin', terms => ({
  options: {
    sort: terms.sort
  }
}));

Users.addView("usersWithBannedUsers", function () {
  return {
    selector: {
      bannedUserIds: {$exists: true}
    },
  }
})

Users.addView("sunshineNewUsers", function () {
  return {
    selector: {
      $or: [
        { voteCount: {$gt: 0}},
        { commentCount: {$gt: 0}},
        { postCount: {$gt: 0}},
      ],
      reviewedByUserId: {$exists: false},
      banned: {$exists: false},
    },
  }
})
<|endoftext|>


<commit_before>/* eslint-env node, es6 */

'use strict';

const
  DOC_ROOT = __dirname,
  PORT = 8080,

  http = require('http'),
  staticAlias = require('node-static-alias'),
  log4js = require('log4js'),
  filelist = require('stats-filelist'),
  path = require('path'),
  fs = require('fs'),

  MODULE_PACKAGES = [
    'jasmine-core',
    'test-page-loader',
    'cssprefix'
  ],

  EXT_DIR = path.resolve('../../test-ext');

log4js.configure({
  appenders: [
    {
      type: 'console',
      layout: {
        type: 'pattern',
        pattern: '%[[%r]%] %m' // Super simple format
      }
    }
  ]
});
let logger = log4js.getLogger('node-static-alias');
logger.setLevel(log4js.levels.INFO);

http.createServer((request, response) => {
  request.addListener('end', () => {
    (new staticAlias.Server(DOC_ROOT, {
      cache: false,
      alias: MODULE_PACKAGES.map(packageName => (
        { // node_modules
          match: new RegExp(`^/${packageName}/.+`),
          serve: `${require.resolve(packageName).replace(/([\/\\]node_modules)[\/\\].*$/, '$1')}<% reqPath %>`,
          allowOutside: true
        })).concat([
          // test-ext
          {
            match: /^\/ext\/.+/,
            serve: params => params.reqPath.replace(/^\/ext/, EXT_DIR),
            allowOutside: true
          },
          // test-ext index
          {
            match: /^\/ext\/?$/,
            serve: () => {
              const indexPath = path.join(EXT_DIR, '.index.html');
              fs.writeFileSync(indexPath,
                `<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
${
                  filelist.getSync(EXT_DIR, {
                    filter: stats => /^[^\.].*\.html$/.test(stats.name),
                    listOf: 'fullPath'
                  }).sort()
                  .map(fullPath => {
                    const htmlPath = path.relative(EXT_DIR, fullPath).replace(path.sep, '/');
                    return `<li><a href="${htmlPath}">${htmlPath}</a></li>`;
                  }).join('')
                }
`);
              return indexPath;
            },
            allowOutside: true
          }
        ]),
      logger: logger
    }))
    .serve(request, response, e => {
      if (e) {
        response.writeHead(e.status, e.headers);
        logger.error('(%s) %s', request.url, response.statusCode);
        if (e.status === 404) {
          response.end('Not Found');
        }
      } else {
        logger.info('(%s) %s', request.url, response.statusCode);
      }
    });
  }).resume();
}).listen(PORT);

console.log(`START: http://localhost:${PORT}/\nROOT: ${DOC_ROOT}`);
console.log('(^C to stop)');
<commit_msg>Fix path to test-ext<commit_after>/* eslint-env node, es6 */

'use strict';

const
  DOC_ROOT = __dirname,
  PORT = 8080,

  http = require('http'),
  staticAlias = require('node-static-alias'),
  log4js = require('log4js'),
  filelist = require('stats-filelist'),
  path = require('path'),
  fs = require('fs'),

  MODULE_PACKAGES = [
    'jasmine-core',
    'test-page-loader',
    'cssprefix'
  ],

  EXT_DIR = path.resolve(__dirname, '../../test-ext');

log4js.configure({
  appenders: [
    {
      type: 'console',
      layout: {
        type: 'pattern',
        pattern: '%[[%r]%] %m' // Super simple format
      }
    }
  ]
});
let logger = log4js.getLogger('node-static-alias');
logger.setLevel(log4js.levels.INFO);

http.createServer((request, response) => {
  request.addListener('end', () => {
    (new staticAlias.Server(DOC_ROOT, {
      cache: false,
      alias: MODULE_PACKAGES.map(packageName => (
        { // node_modules
          match: new RegExp(`^/${packageName}/.+`),
          serve: `${require.resolve(packageName).replace(/([\/\\]node_modules)[\/\\].*$/, '$1')}<% reqPath %>`,
          allowOutside: true
        })).concat([
          // test-ext
          {
            match: /^\/ext\/.+/,
            serve: params => params.reqPath.replace(/^\/ext/, EXT_DIR),
            allowOutside: true
          },
          // test-ext index
          {
            match: /^\/ext\/?$/,
            serve: () => {
              const indexPath = path.join(EXT_DIR, '.index.html');
              fs.writeFileSync(indexPath,
                `<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
${
                  filelist.getSync(EXT_DIR, {
                    filter: stats => /^[^\.].*\.html$/.test(stats.name),
                    listOf: 'fullPath'
                  }).sort()
                  .map(fullPath => {
                    const htmlPath = path.relative(EXT_DIR, fullPath).replace(path.sep, '/');
                    return `<li><a href="${htmlPath}">${htmlPath}</a></li>`;
                  }).join('')
                }
`);
              return indexPath;
            },
            allowOutside: true
          }
        ]),
      logger: logger
    }))
    .serve(request, response, e => {
      if (e) {
        response.writeHead(e.status, e.headers);
        logger.error('(%s) %s', request.url, response.statusCode);
        if (e.status === 404) {
          response.end('Not Found');
        }
      } else {
        logger.info('(%s) %s', request.url, response.statusCode);
      }
    });
  }).resume();
}).listen(PORT);

console.log(`START: http://localhost:${PORT}/\nROOT: ${DOC_ROOT}`);
console.log('(^C to stop)');
<|endoftext|>


<commit_before>var Keys = require('./Keys');
var parseTree = require('./parse-tree');
var matcher = require('./operations/matcher');
var Rx = require('rx');
var normalizePathSets = require('./operations/ranges/normalizePathSets');
var recurseMatchAndExecute = require('./run/recurseMatchAndExecute');
var optimizePathSets = require('./cache/optimizePathSets');
var pathValueMerge = require('./cache/pathValueMerge');
var runGetAction = require('./run/get/runGetAction');
var runSetAction = require('./run/set/runSetAction');
var runCallAction = require('./run/call/runCallAction');
var $atom = require('./support/types').$atom;
var get = 'get';
var set = 'set';
var call = 'call';
var MAX_REF_FOLLOW = 50;

// TODO: We should move this into the constructor.
Rx.config.longStackSupport = true;

var Router = function(routes, options) {
    var opts = options || {};

    this._routes = routes;
    this._rst = parseTree(routes);
    this._get = matcher(this._rst);
    this._set = matcher(this._rst);
    this._call = matcher(this._rst);
    this._debug = opts.debug;
    this.maxRefFollow = opts.maxRefFollow || MAX_REF_FOLLOW;
};

Router.prototype = {
    get: function(paths) {
        var jsongCache = {};
        var action = runGetAction(this, jsongCache);
        var router = this;
        return run(this._get, action, normalizePathSets(paths), get, this, jsongCache).
            map(function(jsongEnv) {
                return materializeMissing(router, paths, jsongEnv);
            });
    },

    set: function(jsong) {
        // TODO: Remove the modelContext and replace with just jsongEnv
        // when http://github.com/Netflix/falcor-router/issues/24 is addressed
        var jsongCache = {};
        var action = runSetAction(this, jsong, jsongCache);
        var router = this;
        return run(this._set, action, jsong.paths, set, this, jsongCache).
            map(function(jsongEnv) {
                return materializeMissing(router, jsong.paths, jsongEnv);
            });
    },

    call: function(callPath, args, suffixes, paths) {
        var jsongCache = {};
        var action = runCallAction(this, callPath, args, suffixes, paths, jsongCache);
        var callPaths = [callPath];
        var router = this;
        return run(this._call, action, callPaths, call, this, jsongCache).
            map(function(jsongResult) {
                var jsongEnv = materializeMissing(
                    router,
                    callPaths,
                    jsongResult,
                    {
                        $type: $atom,
                        $expires: 0
                    });

                jsongEnv.paths = jsongResult.reportedPaths.concat(callPaths);
                return jsongEnv;
            });
    }
};

function run(matcherFn, actionRunner, paths, method, routerInstance, jsongCache) {
    return recurseMatchAndExecute(
            matcherFn, actionRunner, paths, method, routerInstance, jsongCache);
}

function materializeMissing(router, paths, jsongEnv, missingAtom) {
    var jsong = jsongEnv.jsong;
    var materializedAtom = missingAtom || {$type: $atom};

    // Optimizes the pathSets from the jsong then
    // inserts atoms of undefined.
    optimizePathSets(jsong, paths, router.maxRefFollow).
        forEach(function(optMissingPath) {
            pathValueMerge(jsong, {
                path: optMissingPath,
                value: materializedAtom
            });
        });

    return {jsong: jsong};
}

Router.ranges = Keys.ranges;
Router.integers = Keys.integers;
Router.keys = Keys.keys;
module.exports = Router;

<commit_msg>removed merge conflict.<commit_after>var Keys = require('./Keys');
var parseTree = require('./parse-tree');
var matcher = require('./operations/matcher');
var Rx = require('rx');
var normalizePathSets = require('./operations/ranges/normalizePathSets');
var recurseMatchAndExecute = require('./run/recurseMatchAndExecute');
var optimizePathSets = require('./cache/optimizePathSets');
var pathValueMerge = require('./cache/pathValueMerge');
var runGetAction = require('./run/get/runGetAction');
var runSetAction = require('./run/set/runSetAction');
var runCallAction = require('./run/call/runCallAction');
var $atom = require('./support/types').$atom;
var get = 'get';
var set = 'set';
var call = 'call';
var MAX_REF_FOLLOW = 50;

// TODO: We should move this into the constructor.
Rx.config.longStackSupport = true;

var Router = function(routes, options) {
    var opts = options || {};

    this._routes = routes;
    this._rst = parseTree(routes);
    this._get = matcher(this._rst);
    this._set = matcher(this._rst);
    this._call = matcher(this._rst);
    this._debug = opts.debug;
    this.maxRefFollow = opts.maxRefFollow || MAX_REF_FOLLOW;
};

Router.createClass = function(routes) {
  function C(options) {
    this._debug = options.debug;
  };

  C.prototype = new Router(routes);
  C.prototype.constructor = C;

  return C;
};

Router.prototype = {
    get: function(paths) {
        var jsongCache = {};
        var action = runGetAction(this, jsongCache);
        var router = this;
        return run(this._get, action, normalizePathSets(paths), get, this, jsongCache).
            map(function(jsongEnv) {
                return materializeMissing(router, paths, jsongEnv);
            });
    },

    set: function(jsong) {
        // TODO: Remove the modelContext and replace with just jsongEnv
        // when http://github.com/Netflix/falcor-router/issues/24 is addressed
        var jsongCache = {};
        var action = runSetAction(this, jsong, jsongCache);
        var router = this;
        return run(this._set, action, jsong.paths, set, this, jsongCache).
            map(function(jsongEnv) {
                return materializeMissing(router, jsong.paths, jsongEnv);
            });
    },

    call: function(callPath, args, suffixes, paths) {
        var jsongCache = {};
        var action = runCallAction(this, callPath, args, suffixes, paths, jsongCache);
        var callPaths = [callPath];
        var router = this;
        return run(this._call, action, callPaths, call, this, jsongCache).
            map(function(jsongResult) {
                var jsongEnv = materializeMissing(
                    router,
                    callPaths,
                    jsongResult,
                    {
                        $type: $atom,
                        $expires: 0
                    });

                jsongEnv.paths = jsongResult.reportedPaths.concat(callPaths);
                return jsongEnv;
            });
    }
};

function run(matcherFn, actionRunner, paths, method, routerInstance, jsongCache) {
    return recurseMatchAndExecute(
            matcherFn, actionRunner, paths, method, routerInstance, jsongCache);
}

function materializeMissing(router, paths, jsongEnv, missingAtom) {
    var jsong = jsongEnv.jsong;
    var materializedAtom = missingAtom || {$type: $atom};

    // Optimizes the pathSets from the jsong then
    // inserts atoms of undefined.
    optimizePathSets(jsong, paths, router.maxRefFollow).
        forEach(function(optMissingPath) {
            pathValueMerge(jsong, {
                path: optMissingPath,
                value: materializedAtom
            });
        });

    return {jsong: jsong};
}

Router.ranges = Keys.ranges;
Router.integers = Keys.integers;
Router.keys = Keys.keys;
module.exports = Router;

<|endoftext|>


<commit_before>var ko = require('knockout');
require('knockout-punches');
var $ = require('jquery');
var bootbox = require('bootbox');
var Raven = require('raven-js');

var FolderPicker = require('folderpicker');
var $osf = require('osfHelpers');
var ctx = window.contextVars;

ko.punches.enableAll();
/**
 * Knockout view model for citations node settings widget.
 */
var CitationsFolderPickerViewModel = function(name, url, selector, folderPicker) {
    var self = this;

    self.url = url;
    self.properName = name.charAt(0).toUpperCase() + name.slice(1);
    self.selector = selector;
    // CSS selector for the folder picker div
    self.folderPicker = folderPicker;

    // DEFAULTS
    self.accounts = ko.observableArray([]);
    // Auth information
    self.nodeHasAuth = ko.observable(false);
    // whether current user is authorizer of the addon
    self.userIsOwner = ko.observable(false);
    // whether current user has an auth token
    self.userHasAuth = ko.observable(false);
    // whether the auth token is valid
    self.validCredentials = ko.observable(true);
    self.userAccountId = ko.observable('');
    // Currently linked folder, an Object of the form {name: ..., id: ...}
    self.folder = ko.observable({
        name: '',
        id: ''
    });
    self.ownerName = ko.observable('');
    self.urls = ko.observable({});
    // Flashed messages
    self.message = ko.observable('');
    self.messageClass = ko.observable('text-info');
    // Display names
    self.PICKER = 'picker';
    // Current folder display
    self.currentDisplay = ko.observable(false);
    // Currently selected folder name
    self.selected = ko.observable(false);
    self.loading = ko.observable(false);
    // Whether the initial data has been fetched form the server. Used for
    // error handling.
    self.loadedSettings = ko.observable(false);
    // Whether the mendeley folders have been loaded from the server/mendeley API
    self.loadedFolders = ko.observable(false);

    self.messages = {
        INVALID_CRED_OWNER: ko.computed(function() {
            return 'Could not retrieve ' + self.properName + ' settings at ' +
                'this time. The ' + self.properName + ' addon credentials may no longer be valid.' +
                ' Try deauthorizing and reauthorizing ' + self.properName + ' on your account settings page.';
        }),
        INVALID_CRED_NOT_OWNER: ko.computed(function() {
            return 'Could not retrieve ' + self.properName + ' settings at ' +
                'this time. The ' + self.properName + ' addon credentials may no longer be valid.' +
                ' Contact ' + self.ownerName() + ' to verify.';
        }),
        CANT_RETRIEVE_SETTINGS: ko.computed(function() {
            return 'Could not retrieve ' + self.properName + ' settings at ' +
                'this time. Please refresh ' +
                'the page. If the problem persists, email ' +
                'support@osf.io.';
        }),
        UPDATE_ACCOUNTS_ERROR: ko.computed(function() {
            return 'Could not retrieve ' + self.properName + ' account list at ' +
                'this time. Please refresh the page. If the problem persists, email ' +
                'support@osf.io.';
        }),
        DEAUTHORIZE_SUCCESS: ko.computed(function() {
            return 'Deauthorized ' + self.properName + '.';
        }),
        DEAUTHORIZE_FAIL: ko.computed(function() {
            return 'Could not deauthorize because of an error. Please try again later.';
        }),
        CONNECT_ACCOUNT_SUCCESS: ko.computed(function() {
            return 'Successfully created a Mendeley Access Token';
        }),
        SUBMIT_SETTINGS_SUCCESS: ko.computed(function() {
            var overviewURL = window.contextVars.node.urls.web;
            return 'Successfully linked "' + self.folder() + '". Go to the Overview page to view your citations.';
        }),
        SUBMIT_SETTINGS_ERROR: ko.computed(function() {
            return 'Could not change settings. Please try again later.';
        }),
        CONFIRM_DEAUTH: ko.computed(function() {
            return 'Are you sure you want to remove this ' + self.properName + ' authorization?';
        }),
        CONFIRM_AUTH: ko.computed(function() {
            return 'Are you sure you want to authorize this project with your ' + self.properName + ' access token?';
        }),
        TOKEN_IMPORT_ERROR: ko.computed(function() {
            return 'Error occurred while importing access token.';
        }),
        CONNECT_ERROR: ko.computed(function() {
            return 'Could not connect to ' + self.properName + ' at this time. Please try again later.';
        })
    };

    /**
     * Whether or not to show the Import Access Token Button
     */
    self.showImport = ko.computed(function() {
        // Invoke the observables to ensure dependency tracking
        var userHasAuth = self.userHasAuth();
        var nodeHasAuth = self.nodeHasAuth();
        var loaded = self.loadedSettings();
        return userHasAuth && !nodeHasAuth && loaded;
    });

    /** Whether or not to show the full settings pane. */
    self.showSettings = ko.computed(function() {
        return self.nodeHasAuth();
    });

    /** Whether or not to show the Create Access Token button */
    self.showTokenCreateButton = ko.computed(function() {
        // Invoke the observables to ensure dependency tracking
        var userHasAuth = self.userHasAuth();
        var nodeHasAuth = self.nodeHasAuth();
        var loaded = self.loadedSettings();
        return !userHasAuth && !nodeHasAuth && loaded;
    });

    /** Computed functions for the linked and selected folders' display text.*/
    self.folderName = ko.computed(function() {
        var nodeHasAuth = self.nodeHasAuth();
        var folder = self.folder();
        return (nodeHasAuth && folder) ? folder.name : '';
    });

    self.selectedFolderName = ko.computed(function() {
        var userIsOwner = self.userIsOwner();
        var selected = self.selected();
        return (userIsOwner && selected) ? selected.name : '';
    });

    // Initial fetch from server
    self.fetchFromServer();
};

/** Change the flashed message. */
CitationsFolderPickerViewModel.prototype.changeMessage = function(text, css, timeout) {
    var self = this;

    self.message(text);
    var cssClass = css || 'text-info';
    self.messageClass(cssClass);
    if (timeout) {
        // Reset message after timeout period
        setTimeout(function() {
            self.message('');
            self.messageClass('text-info');
        }, timeout);
    }
};

/**
 * Update the view model from data returned from the server.
 */
CitationsFolderPickerViewModel.prototype.updateFromData = function(data) {
    var self = this;
    self.ownerName(data.ownerName);
    self.nodeHasAuth(data.nodeHasAuth);
    self.userIsOwner(data.userIsOwner);
    self.userHasAuth(data.userHasAuth);
    self.userAccountId(data.userAccountId);
    self.folder(data.folder || 'None');
    self.urls(data.urls);
    // ATM equivalent on backend
    self.validCredentials(data.nodeHasAuth);
};

CitationsFolderPickerViewModel.prototype.fetchFromServer = function() {
    var self = this;
    var request = $.ajax({
        url: self.url,
        type: 'GET',
        dataType: 'json'
    });

    request.done(function(response) {
        self.updateFromData(response);
        self.loadedSettings(true);
        if (!self.validCredentials()) {
            if (self.userIsOwner()) {
                self.changeMessage(self.messages.INVALID_CRED_OWNER(), 'text-warning');
            } else {
                self.changeMessage(self.messages.INVALID_CRED_NOT_OWNER(), 'text-warning');
            }
        }
    });
    request.fail(function(xhr, textStatus, error) {
        self.changeMessage(self.messages.CANT_RETRIEVE_SETTINGS(), 'text-warning');
        Raven.captureMessage('Could not GET Mendeley settings', {
            url: self.url,
            textStatus: textStatus,
            error: error
        });
    });
};

CitationsFolderPickerViewModel.prototype.updateAccounts = function(callback) {
    var self = this;
    var request = $.get(self.urls().accounts);
    request.done(function(data) {
        self.accounts(data.accounts.map(function(account) {
            return {
                name: account.display_name,
                id: account.id
            };
        }));
        callback();
    });
    request.fail(function(xhr, textStatus, error) {
        self.changeMessage(self.messages.UPDATE_ACCOUNTS_ERROR(), 'text-warning');
        Raven.captureMessage('Could not GET mendeley accounts for user', {
            url: self.url,
            textStatus: textStatus,
            error: error
        });
    });
};
/**
 * Allows a user to create a Menedeley access token from the nodeSettings page
 */
CitationsFolderPickerViewModel.prototype.connectAccount = function() {
    var self = this;

    window.oauthComplete = function(res) {
        // Update view model based on response
        self.changeMessage(self.messages.CONNECT_ACCOUNT_SUCCESS(), 'text-success', 3000);
        self.updateAccounts(function() {
            $osf.postJSON(
                self.urls().importAuth, {
                    external_account_id: self.accounts()[0].id
                }
            ).then(self.onImportSuccess, self.onImportError);
        });
    };
    window.open(self.urls().auth);
};

CitationsFolderPickerViewModel.prototype.connectExistingAccount = function(account_id) {
    var self = this;

    $osf.postJSON(
        self.urls().importAuth, {
            external_account_id: account_id
        }
    ).then(self.onImportSuccess, self.onImportError);
};

/**
 * Send a PUT request to change the linked mendeley folder.
 */
CitationsFolderPickerViewModel.prototype.submitSettings = function() {
    var self = this;

    function onSubmitSuccess(response) {
        self.changeMessage(self.messages.SUBMIT_SETTINGS_SUCCESS(), 'text-success', 5000);
        self.folder(self.selected().name);
        self.cancelSelection();
    }

    function onSubmitError() {
        self.changeMessage(self.messages.SUBMIT_SETTINGS_ERROR(), 'text-danger');
    }

    $osf.putJSON(self.urls().config, {
            external_account_id: self.userAccountId(),
            external_list_id: self.selected().id
        })
        .done(onSubmitSuccess)
        .fail(onSubmitError);
};

/**
 * Must be used to update radio buttons and knockout view model simultaneously
 */
CitationsFolderPickerViewModel.prototype.cancelSelection = function() {
    this.selected(null);
};

/** Pop up a confirmation to deauthorize mendeley from this node.
 *  Send DELETE request if confirmed.
 */
CitationsFolderPickerViewModel.prototype.deauthorize = function() {
    var self = this;

    /**
     * Send DELETE request to deauthorize this node.
     */
    function sendDeauth() {
        var request = $.ajax({
            url: self.urls().deauthorize,
            type: 'DELETE'
        });

        request.done(function() {
            // Update observables
            self.nodeHasAuth(false);
            self.cancelSelection();
            self.currentDisplay(null);
            self.changeMessage(self.messages.DEAUTHORIZE_SUCCESS(), 'text-warning', 3000);
        });

        request.fail(function(xhr, textStatus, error) {
            self.changeMessage(self.messages.DEAUTHORIZE_FAIL(), 'text-danger');
            Raven.captureMessage('Could not deauthorize mendeley account from node', {
                url: self.urls().deauthorize,
                textStatus: textStatus,
                error: error
            });
        });

        return request;
    }

    bootbox.confirm({
        title: 'Deauthorize ' + self.properName + '?',
        message: self.messages.CONFIRM_DEAUTH(),
        callback: function(confirmed) {
            if (confirmed) {
                return sendDeauth();
            }
        }
    });
};

// Callback for when PUT request to import user access token
CitationsFolderPickerViewModel.prototype.onImportSuccess = function(response) {
    var self = this;

    var msg = response.message || 'Successfully imported access token from profile.';
    // Update view model based on response
    self.changeMessage(msg, 'text-success', 3000);
    self.updateFromData(response.result);
    self.activatePicker();
};

CitationsFolderPickerViewModel.prototype.onImportError = function(xhr, textStatus, error) {
    var self = this;

    self.changeMessage(self.messages.TOKEN_IMPORT_ERROR, 'text-danger');
    Raven.captureMessage('Failed to import Mendeley access token', {
        url: self.urls().importAuth,
        textStatus: textStatus,
        error: error
    });
};

/**
 * Send PUT request to import access token from user profile.
 */
CitationsFolderPickerViewModel.prototype.importAuth = function() {
    var self = this;

    self.updateAccounts(function() {
        if (self.accounts().length > 1) {
            bootbox.prompt({
                title: 'Choose Mendeley Access Token to Import',
                inputType: 'select',
                inputOptions: ko.utils.arrayMap(
                    self.accounts(),
                    function(item) {
                        return {
                            text: item.name,
                            value: item.id
                        };
                    }
                ),
                value: self.accounts()[0].id,
                callback: self.connectExistingAccount
            });
        } else {
            bootbox.confirm({
                title: 'Import ' + self.properName + ' Access Token?',
                message: self.messages.CONFIRM_AUTH(),
                callback: function(confirmed) {
                    if (confirmed) {
                        self.connectExistingAccount(self.accounts()[0].id);
                    }
                }
            });
        }
    });
};

/**
 * Activates the HGrid folder picker.
 */
CitationsFolderPickerViewModel.prototype.activatePicker = function() {
    var self = this;

    /** Callback for chooseFolder action.
     *   Just changes the ViewModel's self.selected observable to the selected
     *   folder.
     */
    function onPickFolder(evt, item) {
        evt.preventDefault();
        self.selected({
            name: item.data.name,
            id: item.data.id
        });
        return false; // Prevent event propagation
    }

    self.currentDisplay(self.PICKER);
    // Only load folders if they haven't already been requested
    if (!self.loadedFolders()) {
        // Show loading indicator
        self.loading(true);
        $(self.folderPicker).folderpicker({
            onPickFolder: onPickFolder,
            initialFolderPath: 'mendeley',
            // Fetch mendeley folders with AJAX
            filesData: self.urls().folders,
            // Lazy-load each folder's contents
            // Each row stores its url for fetching the folders it contains
            resolveLazyloadUrl: function(item) {
                return self.urls().folders + item.data.id + '/?view=folders';
            },
            lazyLoadPreprocess: function(data) {
                return data.contents.filter(function(item) {
                    return item.kind === 'folder';
                });
            },
            oddEvenClass: {
                odd: 'addon-folderpicker-odd',
                even: 'addon-folderpicker-even'
            },
            ajaxOptions: {
                error: function(xhr, textStatus, error) {
                    self.loading(false);
                    self.changeMessage(self.messages.CONNECT_ERROR(), 'text-warning');
                    Raven.captureMessage('Could not GET get ' + self.properName + ' contents.', {
                        textStatus: textStatus,
                        error: error
                    });
                }
            },
            folderPickerOnload: function() {
                // Hide loading indicator
                self.loading(false);
            }
        });
    }
};

/**
 * Toggles the visibility of the folder picker.
 */
CitationsFolderPickerViewModel.prototype.togglePicker = function() {
    var self = this;

    // Toggle visibility of folder picker
    var shown = self.currentDisplay() === self.PICKER;
    if (!shown) {
        self.currentDisplay(self.PICKER);
        self.activatePicker();
    } else {
        self.currentDisplay(null);
        // Clear selection
        self.cancelSelection();
    }
};

module.exports = CitationsFolderPickerViewModel;
<commit_msg>Fixes to generic citationsFolderPickerViewModel for Mendeley and Zotero node settings page.<commit_after>var ko = require('knockout');
require('knockout-punches');
var $ = require('jquery');
var bootbox = require('bootbox');
var Raven = require('raven-js');

var FolderPicker = require('folderpicker');
var $osf = require('osfHelpers');
var ctx = window.contextVars;

ko.punches.enableAll();
/**
 * Knockout view model for citations node settings widget.
 */
var CitationsFolderPickerViewModel = function(name, url, selector, folderPicker) {
    var self = this;

    self.url = url;
    self.properName = name.charAt(0).toUpperCase() + name.slice(1);
    self.selector = selector;
    // CSS selector for the folder picker div
    self.folderPicker = folderPicker;

    // DEFAULTS
    self.accounts = ko.observableArray([]);
    // Auth information
    self.nodeHasAuth = ko.observable(false);
    // whether current user is authorizer of the addon
    self.userIsOwner = ko.observable(false);
    // whether current user has an auth token
    self.userHasAuth = ko.observable(false);
    // whether the auth token is valid
    self.validCredentials = ko.observable(true);
    self.userAccountId = ko.observable('');
    // Currently linked folder, an Object of the form {name: ..., id: ...}
    self.folder = ko.observable({
        name: '',
        id: ''
    });
    self.ownerName = ko.observable('');
    self.urls = ko.observable({});
    // Flashed messages
    self.message = ko.observable('');
    self.messageClass = ko.observable('text-info');
    // Display names
    self.PICKER = 'picker';
    // Current folder display
    self.currentDisplay = ko.observable(false);
    // Currently selected folder name
    self.selected = ko.observable(false);
    self.loading = ko.observable(false);
    // Whether the initial data has been fetched form the server. Used for
    // error handling.
    self.loadedSettings = ko.observable(false);
    // Whether the mendeley folders have been loaded from the server/mendeley API
    self.loadedFolders = ko.observable(false);

    self.messages = {
        INVALID_CRED_OWNER: ko.computed(function() {
            return 'Could not retrieve ' + self.properName + ' settings at ' +
                'this time. The ' + self.properName + ' addon credentials may no longer be valid.' +
                ' Try deauthorizing and reauthorizing ' + self.properName + ' on your account settings page.';
        }),
        INVALID_CRED_NOT_OWNER: ko.computed(function() {
            return 'Could not retrieve ' + self.properName + ' settings at ' +
                'this time. The ' + self.properName + ' addon credentials may no longer be valid.' +
                ' Contact ' + self.ownerName() + ' to verify.';
        }),
        CANT_RETRIEVE_SETTINGS: ko.computed(function() {
            return 'Could not retrieve ' + self.properName + ' settings at ' +
                'this time. Please refresh ' +
                'the page. If the problem persists, email ' +
                'support@osf.io.';
        }),
        UPDATE_ACCOUNTS_ERROR: ko.computed(function() {
            return 'Could not retrieve ' + self.properName + ' account list at ' +
                'this time. Please refresh the page. If the problem persists, email ' +
                'support@osf.io.';
        }),
        DEAUTHORIZE_SUCCESS: ko.computed(function() {
            return 'Deauthorized ' + self.properName + '.';
        }),
        DEAUTHORIZE_FAIL: ko.computed(function() {
            return 'Could not deauthorize because of an error. Please try again later.';
        }),
        CONNECT_ACCOUNT_SUCCESS: ko.computed(function() {
            return 'Successfully created a Mendeley Access Token';
        }),
        SUBMIT_SETTINGS_SUCCESS: ko.computed(function() {
            var overviewURL = window.contextVars.node.urls.web;
            return 'Successfully linked "' + self.folder() + '". Go to the Overview page to view your citations.';
        }),
        SUBMIT_SETTINGS_ERROR: ko.computed(function() {
            return 'Could not change settings. Please try again later.';
        }),
        CONFIRM_DEAUTH: ko.computed(function() {
            return 'Are you sure you want to remove this ' + self.properName + ' authorization?';
        }),
        CONFIRM_AUTH: ko.computed(function() {
            return 'Are you sure you want to authorize this project with your ' + self.properName + ' access token?';
        }),
        TOKEN_IMPORT_ERROR: ko.computed(function() {
            return 'Error occurred while importing access token.';
        }),
        CONNECT_ERROR: ko.computed(function() {
            return 'Could not connect to ' + self.properName + ' at this time. Please try again later.';
        })
    };

    /**
     * Whether or not to show the Import Access Token Button
     */
    self.showImport = ko.computed(function() {
        // Invoke the observables to ensure dependency tracking
        var userHasAuth = self.userHasAuth();
        var nodeHasAuth = self.nodeHasAuth();
        var loaded = self.loadedSettings();
        return userHasAuth && !nodeHasAuth && loaded;
    });

    /** Whether or not to show the full settings pane. */
    self.showSettings = ko.computed(function() {
        return self.nodeHasAuth();
    });

    /** Whether or not to show the Create Access Token button */
    self.showTokenCreateButton = ko.computed(function() {
        // Invoke the observables to ensure dependency tracking
        var userHasAuth = self.userHasAuth();
        var nodeHasAuth = self.nodeHasAuth();
        var loaded = self.loadedSettings();
        return !userHasAuth && !nodeHasAuth && loaded;
    });

    /** Computed functions for the linked and selected folders' display text.*/
    self.folderName = ko.computed(function() {
        var nodeHasAuth = self.nodeHasAuth();
        var folder = self.folder();
        return (nodeHasAuth && folder) ? folder.name : '';
    });

    self.selectedFolderName = ko.computed(function() {
        var userIsOwner = self.userIsOwner();
        var selected = self.selected();
        return (userIsOwner && selected) ? selected.name : '';
    });

    // Initial fetch from server
    self.fetchFromServer();
};

/** Change the flashed message. */
CitationsFolderPickerViewModel.prototype.changeMessage = function(text, css, timeout) {
    var self = this;

    self.message(text);
    var cssClass = css || 'text-info';
    self.messageClass(cssClass);
    if (timeout) {
        // Reset message after timeout period
        setTimeout(function() {
            self.message('');
            self.messageClass('text-info');
        }, timeout);
    }
};

/**
 * Update the view model from data returned from the server.
 */
CitationsFolderPickerViewModel.prototype.updateFromData = function(data) {
    var self = this;
    self.ownerName(data.ownerName);
    self.nodeHasAuth(data.nodeHasAuth);
    self.userIsOwner(data.userIsOwner);
    self.userHasAuth(data.userHasAuth);
    self.userAccountId(data.userAccountId);
    self.folder(data.folder || 'None');
    self.urls(data.urls);
    // ATM equivalent on backend
    self.validCredentials(data.nodeHasAuth);
};

CitationsFolderPickerViewModel.prototype.fetchFromServer = function() {
    var self = this;
    var request = $.ajax({
        url: self.url,
        type: 'GET',
        dataType: 'json'
    });

    request.done(function(response) {
        self.updateFromData(response);
        self.loadedSettings(true);
        if (!self.validCredentials()) {
            if (self.userIsOwner()) {
                self.changeMessage(self.messages.INVALID_CRED_OWNER(), 'text-warning');
            } else {
                self.changeMessage(self.messages.INVALID_CRED_NOT_OWNER(), 'text-warning');
            }
        }
    });
    request.fail(function(xhr, textStatus, error) {
        self.changeMessage(self.messages.CANT_RETRIEVE_SETTINGS(), 'text-warning');
        Raven.captureMessage('Could not GET Mendeley settings', {
            url: self.url,
            textStatus: textStatus,
            error: error
        });
    });
};

CitationsFolderPickerViewModel.prototype.updateAccounts = function(callback) {
    var self = this;
    var request = $.get(self.urls().accounts);
    request.done(function(data) {
        self.accounts(data.accounts.map(function(account) {
            return {
                name: account.display_name,
                id: account.id
            };
        }));
        callback();
    });
    request.fail(function(xhr, textStatus, error) {
        self.changeMessage(self.messages.UPDATE_ACCOUNTS_ERROR(), 'text-warning');
        Raven.captureMessage('Could not GET mendeley accounts for user', {
            url: self.url,
            textStatus: textStatus,
            error: error
        });
    });
};
/**
 * Allows a user to create a Menedeley access token from the nodeSettings page
 */
CitationsFolderPickerViewModel.prototype.connectAccount = function() {
    var self = this;

    window.oauthComplete = function(res) {
        // Update view model based on response
        self.changeMessage(self.messages.CONNECT_ACCOUNT_SUCCESS(), 'text-success', 3000);
        self.updateAccounts(function() {
            $osf.postJSON(
                self.urls().importAuth, {
                    external_account_id: self.accounts()[0].id
                }
            ).then(self.onImportSuccess.bind(self), self.onImportError.bind(self));
        });
    };
    window.open(self.urls().auth);
};

CitationsFolderPickerViewModel.prototype.connectExistingAccount = function(account_id) {
    var self = this;

    $osf.postJSON(
        self.urls().importAuth, {
            external_account_id: account_id
        }
    ).then(self.onImportSuccess.bind(self), self.onImportError.bind(self));
};

/**
 * Send a PUT request to change the linked mendeley folder.
 */
CitationsFolderPickerViewModel.prototype.submitSettings = function() {
    var self = this;

    function onSubmitSuccess(response) {
        self.changeMessage(self.messages.SUBMIT_SETTINGS_SUCCESS(), 'text-success', 5000);
        self.folder(self.selected().name);
        self.cancelSelection();
    }

    function onSubmitError() {
        self.changeMessage(self.messages.SUBMIT_SETTINGS_ERROR(), 'text-danger');
    }

    $osf.putJSON(self.urls().config, {
            external_account_id: self.userAccountId(),
            external_list_id: self.selected().id
        })
        .done(onSubmitSuccess)
        .fail(onSubmitError);
};

/**
 * Must be used to update radio buttons and knockout view model simultaneously
 */
CitationsFolderPickerViewModel.prototype.cancelSelection = function() {
    this.selected(null);
};

/** Pop up a confirmation to deauthorize mendeley from this node.
 *  Send DELETE request if confirmed.
 */
CitationsFolderPickerViewModel.prototype.deauthorize = function() {
    var self = this;

    /**
     * Send DELETE request to deauthorize this node.
     */
    function sendDeauth() {
        var request = $.ajax({
            url: self.urls().deauthorize,
            type: 'DELETE'
        });

        request.done(function() {
            // Update observables
            self.nodeHasAuth(false);
            self.cancelSelection();
            self.currentDisplay(null);
            self.changeMessage(self.messages.DEAUTHORIZE_SUCCESS(), 'text-warning', 3000);
        });

        request.fail(function(xhr, textStatus, error) {
            self.changeMessage(self.messages.DEAUTHORIZE_FAIL(), 'text-danger');
            Raven.captureMessage('Could not deauthorize mendeley account from node', {
                url: self.urls().deauthorize,
                textStatus: textStatus,
                error: error
            });
        });

        return request;
    }

    bootbox.confirm({
        title: 'Deauthorize ' + self.properName + '?',
        message: self.messages.CONFIRM_DEAUTH(),
        callback: function(confirmed) {
            if (confirmed) {
                return sendDeauth();
            }
        }
    });
};

// Callback for when PUT request to import user access token
CitationsFolderPickerViewModel.prototype.onImportSuccess = function(response) {
    var self = this;

    var msg = response.message || 'Successfully imported access token from profile.';
    // Update view model based on response
    self.changeMessage(msg, 'text-success', 3000);
    self.updateFromData(response.result);
    self.activatePicker();
};

CitationsFolderPickerViewModel.prototype.onImportError = function(xhr, textStatus, error) {
    var self = this;

    self.changeMessage(self.messages.TOKEN_IMPORT_ERROR(), 'text-danger');
    Raven.captureMessage('Failed to import Mendeley access token', {
        url: self.urls().importAuth,
        textStatus: textStatus,
        error: error
    });
};

/**
 * Send PUT request to import access token from user profile.
 */
CitationsFolderPickerViewModel.prototype.importAuth = function() {
    var self = this;

    self.updateAccounts(function() {
        if (self.accounts().length > 1) {
            bootbox.prompt({
                title: 'Choose Mendeley Access Token to Import',
                inputType: 'select',
                inputOptions: ko.utils.arrayMap(
                    self.accounts(),
                    function(item) {
                        return {
                            text: item.name,
                            value: item.id
                        };
                    }
                ),
                value: self.accounts()[0].id,
                callback: self.connectExistingAccount
            });
        } else {
            bootbox.confirm({
                title: 'Import ' + self.properName + ' Access Token?',
                message: self.messages.CONFIRM_AUTH(),
                callback: function(confirmed) {
                    if (confirmed) {
                        self.connectExistingAccount(self.accounts()[0].id);
                    }
                }
            });
        }
    });
};

/**
 * Activates the HGrid folder picker.
 */
CitationsFolderPickerViewModel.prototype.activatePicker = function() {
    var self = this;

    /** Callback for chooseFolder action.
     *   Just changes the ViewModel's self.selected observable to the selected
     *   folder.
     */
    function onPickFolder(evt, item) {
        evt.preventDefault();
        self.selected({
            name: item.data.name,
            id: item.data.id
        });
        return false; // Prevent event propagation
    }

    self.currentDisplay(self.PICKER);
    // Only load folders if they haven't already been requested
    if (!self.loadedFolders()) {
        // Show loading indicator
        self.loading(true);
        $(self.folderPicker).folderpicker({
            onPickFolder: onPickFolder,
            initialFolderPath: 'mendeley',
            // Fetch mendeley folders with AJAX
            filesData: self.urls().folders,
            // Lazy-load each folder's contents
            // Each row stores its url for fetching the folders it contains
            resolveLazyloadUrl: function(item) {
                return self.urls().folders + item.data.id + '/?view=folders';
            },
            lazyLoadPreprocess: function(data) {
                return data.contents.filter(function(item) {
                    return item.kind === 'folder';
                });
            },
            oddEvenClass: {
                odd: 'addon-folderpicker-odd',
                even: 'addon-folderpicker-even'
            },
            ajaxOptions: {
                error: function(xhr, textStatus, error) {
                    self.loading(false);
                    self.changeMessage(self.messages.CONNECT_ERROR(), 'text-warning');
                    Raven.captureMessage('Could not GET get ' + self.properName + ' contents.', {
                        textStatus: textStatus,
                        error: error
                    });
                }
            },
            folderPickerOnload: function() {
                // Hide loading indicator
                self.loading(false);
            }
        });
    }
};

/**
 * Toggles the visibility of the folder picker.
 */
CitationsFolderPickerViewModel.prototype.togglePicker = function() {
    var self = this;

    // Toggle visibility of folder picker
    var shown = self.currentDisplay() === self.PICKER;
    if (!shown) {
        self.currentDisplay(self.PICKER);
        self.activatePicker();
    } else {
        self.currentDisplay(null);
        // Clear selection
        self.cancelSelection();
    }
};

module.exports = CitationsFolderPickerViewModel;
<|endoftext|>