<commit_before>// This is binnedData. A convenient way of storing binned data

//{{{ CONSTANTS
var MAX_NUMBER_OF_BIN_LEVELS = 34; // keep sync'd with ../binnedChart.js
  // TODO: phase this out (preferable) OR set it as a really high number

/// CONSTANTS }}}

binnedData = function () {

  //{{{ VARIABLES
  var bd = { // where all of the data is stored
    keys : ['average', 'maxes', 'mins', 'q1', 'q3'],
    rawData : {
      color: '#000',
      opacity: 0.5,
      levels: [], // stores all of the values for each level in an array.
                  // example: [[{val: 1.7, ms: ms_since_epoch}, {val: 2.3, ms: ms_since_epoch}], [etc.]]
    },
    average : {
      color : '#F00',
      opacity: 1,
      func   : function (a, b) { return (a+b)/2; },
      levels: [],
    },
    maxes : {
      color : '#000FB5',
      opacity: 1,
      func   : function (a, b) { return d3.max([a,b]); },
      levels: [],
    },
    mins : {
      color : '#00B515',
      opacity: 1,
      func   : function (a, b) { return d3.min([a,b]); },
      levels: [],
    },
    q1 : {
      color : '#800',
      opacity: 1,
      func   : function (a, b, c, d) { return average(getTwoSmallest([a, b, c, d])); }, // average the two smallest values from q1 and q3
      levels: [],
    },
    q3 : {
      color : '#800',
      opacity: 1,
      func   : function (a, b, c, d) { return average(getTwoLargest([a, b, c, d])); }, // average the two largest values from q1 and q3
      levels: [],
    },
    quartiles : {
      color : '#800',
      opacity: 0.3,
      //func   : function (a, b, c, d) { return average(getTwoLargest([a, b, c, d])); }, // average the two largest values from q1 and q3
      levels: [],
    },
  }; // where everything is stored

  // VARIABLES }}}

  //{{{ HELPER METHODS

  function rebin () {
    for (var keyValue in bd.keys) {
      var key = bd.keys[keyValue];
      bd[key].levels[0] = bd.rawData.levels[0]; // update raw data from the source
    }

    // for each level other than raw data level, for each key, bin the data from the lower level
    for (j = 1; j < MAX_NUMBER_OF_BIN_LEVELS; j++){ // for each bin level
      for (var keyValue in bd.keys) { // for each of 'average', 'max', 'min', etc.
        var key = bd.keys[keyValue];

        // store newly calculated data from lower level
        var newData = binTheDataWithFunction(bd, j-1, key, bd[key].func);

        // TODO: filter away duplicates of newData (ms which are already in the old data)

        // get range of new data
        var range = [_.min(newData, function (d) { return d.ms; }).ms,
                     _.max(newData, function (d) { return d.ms; }).ms];

        // TODO remove unused:
        // filter for old data which is outside the range of the new data
        // (newly binned data gets preference over previously binned data)
        var oldFiltered = _.filter(bd[key].levels[j], function (d) { return d.ms < range[0] || d.ms > range[1]; });

        var oldUnfiltered = _.filter(bd[key].levels[j], function (d) { return true; });

        // combine and sort old and new
        //var combo = oldFiltered.concat(newData).sort(function (a, b) { return a.ms - b.ms; });
        //var combo = oldUnfiltered.concat(newData).sort(function (a, b) { return a.ms - b.ms; });
        var combo = combineAndSortArraysOfDateValObjects(oldUnfiltered, newData);

        // store combination
        bd[key].levels[j] = combo;
      } // for each key
    } // for each bin level
  }

  // Bin the data in a level into abstracted bins
  // TODO: This is where the problem is
  //       The bins must all have 0ms as a point-of-reference
  //       so that they don't get rendered differently on the
  //       server and the client.
  //       To fix this, choose whether or not to skip certain
  //       values which are being binned.
  //       If they are being skipped, the next one will be the
  //       first of the two samples to be binned.
  function binTheDataWithFunction (bin, curLevel, key, func) {
    var bDat = new Array();
    var i = 0;
    for(i = 0; i < bin[key].levels[curLevel].length; i = i + 2){
      if (bin[key].levels[curLevel][i+1]){
        var newdate = bin.q1.levels[curLevel][i/*+1*/].ms;

        if (key === 'q1' || key === 'q3') {
          bDat.push({ val:  func(
                bin.q1.levels[curLevel][i].val,
                bin.q1.levels[curLevel][i+1].val,
                bin.q3.levels[curLevel][i].val,
                bin.q3.levels[curLevel][i+1].val)
              , ms: newdate }); // This is messy and depends on a lot of things
        }else{
          bDat.push( { val: func(
                bin[key].levels[curLevel][i].val,
                bin[key].levels[curLevel][i+1].val)
              , ms: newdate });
        }
      }else{
        var newdate = bin[key].levels[curLevel][i].ms;
        bDat.push( { val: bin[key].levels[curLevel][i].val
                   , ms: newdate } );
      }
    }
    return bDat;
  };

  function getTwoLargest (array) {
    var arr = array.slice();
    first = d3.max(arr);
    arr.splice(arr.indexOf(first),1);
    second = d3.max(arr);
    return [first, second];
  };

  function average (array) {
    return d3.sum(array)/array.length;
  };

  function getTwoSmallest (array) {
    var arr = array.slice();
    first = d3.min(arr);
    arr.splice(arr.indexOf(first),1);
    second = d3.min(arr);
    return [first, second];
  };

  function combineAndSortArraysOfDateValObjects (arr1, arr2) {
    // Add the objects from arr2 (array) to arr1 (array)
    //   if the object from arr2 has a ms value which no
    //   object in arr1 has.
    var result = arr1.concat(_.filter(arr2, function(d) {
      var b = !_.some(arr1, function(g) {
        return d.ms === g.ms;
      });
      return b;
    }));

    // sort the result
    result.sort(function (a, b) { return a.ms - b.ms; });
    return result;
  }

  function inAButNotInB(arr1, arr2) {
    return _.filter(arr1, function (d) {
      return !_.contains(arr2, d);
    });
  }

  // HELPER METHODS }}}

  //{{{ INITIALIZATION (runs once)
  // INITIALIZATION }}}

  //{{{ MY (runs whenever something changes)

  var my = function () {
  }

  // MY }}}

  //{{{ PUBLIC METHODS

  my.addRawData = function (rData) {
    // data must be in the following form: (example)
    // [ {val: value_point, ms: ms_since_epoch},
    //   {val: value_point, ms: ms_since_epoch},
    //   {etc...},
    // ],

    if (!bd.rawData.levels[0]) {
      bd.rawData.levels[0] = [];
    }

    for (var i in rData) {
      var dat = rData[i];
      if (_.find(bd.rawData.levels[0], function (d) { return d.ms === dat.ms; })) {
        // We already have that data point
      } else {
        // Add a new object to the bd level
        bd.rawData.levels[0].push({ms: dat.ms, val: dat.val});
      }
    }

    bd.rawData.levels[0].sort(function (a, b) { return a.ms - b.ms; });

    rebin();
    return my;
  }

  my.addBinnedData = function (bData) {
    // data must be in the form of the following example:
    // { average: {
    //     levels: [
    //       [{val: value_point, ms: ms_since_epoch},
    //        {val: value_point, ms: ms_since_epoch},
    //        {etc...}],
    //       [etc.]
    //     ],
    //   },
    //   q1: {
    //     levels: [
    //       [etc.]
    //     ],
    //   },
    //   etc: {},
    // }

    for (var key in bData) { // for each of max_val, min_val, etc.
      for (var lvl in bData[key].levels) { // for each level
        //if we don't have a level for this already, initialize one
        if (!bd[key].levels[lvl]) {
          bd[key].levels[lvl] = [];
        }

        bd[key].levels[lvl] = combineAndSortArraysOfDateValObjects(bd[key].levels[lvl], bData[key].levels[lvl]);
      } // for each received data point
    }; // for each of max_val, min_val, etc.

    rebin();
    return my;
  }

  my.haveDataInRange = function(ms_range, level) {
    // Determine the number of samples which we should have in the given range.

    if (level === 0) {
      key = "rawData";
    } else {
      key = "average";
    }

    var datedRange = my.getDateRange(key, level, ms_range);

    if (datedRange.length === 0) {
      return false;
    }

    var firstSample = datedRange[0].ms;
    var oneSample = 1000 / 200; // milliseconds per sample
    var sampleSize = Math.pow(2, level) * oneSample;

    if (firstSample > ms_range[0] + sampleSize) {
      return false;
    }

    var actualRange = ms_range[1] - firstSample;
    var numberWeShouldHave = Math.floor(actualRange / sampleSize);

    var numberWeHave = datedRange.length;

    return numberWeHave >= numberWeShouldHave;
  }

  my.missingBins = function(ms_range, level) {
    // TODO: Return which bins which we are missing in the given range and level.
    // TODO: returns [[start, end],[start,end],...] ranges of required data

    if (level === 0) {
      key = "rawData";
    } else {
      key = "average";
    }

    var datedRange = my.getDateRange(key, level, ms_range);

    if (datedRange.length === 0) {
      return [ms_range];
    }

    var firstSample = datedRange[0].ms;
    var oneSample = 1000 / 200; // milliseconds per sample
    var sampleSize = Math.pow(2, level) * oneSample;

    var howManyFitAbove = Math.floor((ms_range[1] - firstSample) / sampleSize);
    var howManyFitBelow = Math.floor((firstSample - ms_range[0]) / sampleSize);

    var whichSamplesWeShouldHaveForThisRange = [];

    // add first sample
    whichSamplesWeShouldHaveForThisRange.push(firstSample);

    // add above samples
    _.times(howManyFitAbove, function (i) {
      whichSamplesWeShouldHaveForThisRange.push(firstSample + ((i+1) * sampleSize));
    });

    // add below samples
    _.times(howManyFitBelow, function (i) {
      whichSamplesWeShouldHaveForThisRange.push(firstSample - ((i+1) * sampleSize));
    });

    var missingSamples = inAButNotInB(whichSamplesWeShouldHaveForThisRange, _.pluck(datedRange, 'ms'));

    var missingRanges = []; // form: [[0,1],[1,2],[4,5],[5,6],[6,7]]

    _.each(missingSamples, function (d,i) {
      missingRanges.push([d, d + sampleSize]);
      // missingRanges will now be like this: [[0,1],[1,2],[4,5],[5,6],[6,7]]
    });

    return missingRanges;
  }

  my.getMinRaw = function () {
    return d3.min(bd.rawData.levels[0], function(d) { return d.val; });
  }

  my.getMaxRaw = function () {
    return d3.max(bd.rawData.levels[0], function(d) { return d.val; });
  }

  my.getColor = function (key) {
    return bd[key].color;
  }

  my.getOpacity = function (key) {
    return bd[key].opacity;
  }

  my.getAllInRange = function(lvl, range) {
    // TODO TODO TODO
    // return a bd-like data structure but only
    // with data in the following range and level
    // from all keys

    // initialize the data structure to be sent
    var theKeys = ["average", "q1", "q3", "mins", "maxes"];
    var send_req = {};
    for (var i = 0; i < theKeys.length; i++) {
      send_req[theKeys[i]] = {};
      send_req[theKeys[i]].levels = [];
      send_req[theKeys[i]].levels[lvl] = my.getDateRange(theKeys[i], lvl, range);
    }
    //for(i=0;i<howManyPointsToGenerate;i++) {
    //  var val = randomPoint();
    //  var val_q1 = val - (Math.random() * 1.2);
    //  var val_q3 = val + (Math.random() * 1.2);
    //  var val_min = val_q1 - (Math.random() * 2);
    //  var val_max = val_q3 + (Math.random() * 2);
    //  var dat = parseInt(req.ms_start) - (parseInt(req.ms_start) % msPerBin) - 5; // TODO: magic hack

    //  send_req.average.levels[req.bin_level].push({ms: dat + (i * msPerBin), val: val});
    //  send_req.q1.levels[req.bin_level].push({ms: dat + (i * msPerBin), val: val_q1});
    //  send_req.q3.levels[req.bin_level].push({ms: dat + (i * msPerBin), val: val_q3});
    //  send_req.mins.levels[req.bin_level].push({ms: dat + (i * msPerBin), val: val_min});
    //  send_req.maxes.levels[req.bin_level].push({ms: dat + (i * msPerBin), val: val_max});
    //}
    return send_req;
  }

  my.getDateRange = function (key, lvl, range) {
    // filter an array so that we don't render much more
    // than the required amount of line and area
    return _.filter(bd[key].levels[lvl], function (d, i) {
      return d.ms <= range[1] && d.ms >= range[0];
    });
  }

  my.getKeys = function () {
    return bd.keys.slice(0); // give a copy of the array
  }

  my.bd = function () { // TODO: JUST FOR TESTING
    return bd;
  }

  // PUBLIC METHODS }}}

  return my;
}

/* vim: set foldmethod=marker: */
<commit_msg>...and another thing<commit_after>// This is binnedData. A convenient way of storing binned data

//{{{ CONSTANTS
var MAX_NUMBER_OF_BIN_LEVELS = 34; // keep sync'd with ../binnedChart.js
  // TODO: phase this out (preferable) OR set it as a really high number

/// CONSTANTS }}}

binnedData = function () {

  //{{{ VARIABLES
  var bd = { // where all of the data is stored
    keys : ['average', 'maxes', 'mins', 'q1', 'q3'],
    rawData : {
      color: '#000',
      opacity: 0.5,
      levels: [], // stores all of the values for each level in an array.
                  // example: [[{val: 1.7, ms: ms_since_epoch}, {val: 2.3, ms: ms_since_epoch}], [etc.]]
    },
    average : {
      color : '#F00',
      opacity: 1,
      func   : function (a, b) { return (a+b)/2; },
      levels: [],
    },
    maxes : {
      color : '#000FB5',
      opacity: 1,
      func   : function (a, b) { return d3.max([a,b]); },
      levels: [],
    },
    mins : {
      color : '#00B515',
      opacity: 1,
      func   : function (a, b) { return d3.min([a,b]); },
      levels: [],
    },
    q1 : {
      color : '#800',
      opacity: 1,
      func   : function (a, b, c, d) { return average(getTwoSmallest([a, b, c, d])); }, // average the two smallest values from q1 and q3
      levels: [],
    },
    q3 : {
      color : '#800',
      opacity: 1,
      func   : function (a, b, c, d) { return average(getTwoLargest([a, b, c, d])); }, // average the two largest values from q1 and q3
      levels: [],
    },
    quartiles : {
      color : '#800',
      opacity: 0.3,
      //func   : function (a, b, c, d) { return average(getTwoLargest([a, b, c, d])); }, // average the two largest values from q1 and q3
      levels: [],
    },
  }; // where everything is stored

  // VARIABLES }}}

  //{{{ HELPER METHODS

  function rebin () {
    for (var keyValue in bd.keys) {
      var key = bd.keys[keyValue];
      bd[key].levels[0] = bd.rawData.levels[0]; // update raw data from the source
    }

    // for each level other than raw data level, for each key, bin the data from the lower level
    for (j = 1; j < MAX_NUMBER_OF_BIN_LEVELS; j++){ // for each bin level
      for (var keyValue in bd.keys) { // for each of 'average', 'max', 'min', etc.
        var key = bd.keys[keyValue];

        // store newly calculated data from lower level
        var newData = binTheDataWithFunction(bd, j-1, key, bd[key].func);

        // TODO: filter away duplicates of newData (ms which are already in the old data)

        // get range of new data
        var range = [_.min(newData, function (d) { return d.ms; }).ms,
                     _.max(newData, function (d) { return d.ms; }).ms];

        // TODO remove unused:
        // filter for old data which is outside the range of the new data
        // (newly binned data gets preference over previously binned data)
        var oldFiltered = _.filter(bd[key].levels[j], function (d) { return d.ms < range[0] || d.ms > range[1]; });

        var oldUnfiltered = _.filter(bd[key].levels[j], function (d) { return true; });

        // combine and sort old and new
        //var combo = oldFiltered.concat(newData).sort(function (a, b) { return a.ms - b.ms; });
        //var combo = oldUnfiltered.concat(newData).sort(function (a, b) { return a.ms - b.ms; });
        var combo = combineAndSortArraysOfDateValObjects(oldUnfiltered, newData);

        // store combination
        bd[key].levels[j] = combo;
      } // for each key
    } // for each bin level
  }

  // Bin the data in a level into abstracted bins
  // TODO: This is where the problem is
  //       The bins must all have 0ms as a point-of-reference
  //       so that they don't get rendered differently on the
  //       server and the client.
  //       To fix this, choose whether or not to skip certain
  //       values which are being binned.
  //       If they are being skipped, the next one will be the
  //       first of the two samples to be binned.
  function binTheDataWithFunction (bin, curLevel, key, func) {
    var bDat = new Array();
    var i = 0;
    for(i = 0; i < bin[key].levels[curLevel].length; i = i + 2){
      // TODO: if we are at a bad spot to begin a bin, decrement i by 1 and continue;
      if (bin[key].levels[curLevel][i+1]){
        var newdate = bin.q1.levels[curLevel][i/*+1*/].ms;

        if (key === 'q1' || key === 'q3') {
          bDat.push({ val:  func(
                bin.q1.levels[curLevel][i].val,
                bin.q1.levels[curLevel][i+1].val,
                bin.q3.levels[curLevel][i].val,
                bin.q3.levels[curLevel][i+1].val)
              , ms: newdate }); // This is messy and depends on a lot of things
        }else{
          bDat.push( { val: func(
                bin[key].levels[curLevel][i].val,
                bin[key].levels[curLevel][i+1].val)
              , ms: newdate });
        }
      }else{
        var newdate = bin[key].levels[curLevel][i].ms;
        bDat.push( { val: bin[key].levels[curLevel][i].val
                   , ms: newdate } );
      }
    }
    return bDat;
  };

  function getTwoLargest (array) {
    var arr = array.slice();
    first = d3.max(arr);
    arr.splice(arr.indexOf(first),1);
    second = d3.max(arr);
    return [first, second];
  };

  function average (array) {
    return d3.sum(array)/array.length;
  };

  function getTwoSmallest (array) {
    var arr = array.slice();
    first = d3.min(arr);
    arr.splice(arr.indexOf(first),1);
    second = d3.min(arr);
    return [first, second];
  };

  function combineAndSortArraysOfDateValObjects (arr1, arr2) {
    // Add the objects from arr2 (array) to arr1 (array)
    //   if the object from arr2 has a ms value which no
    //   object in arr1 has.
    var result = arr1.concat(_.filter(arr2, function(d) {
      var b = !_.some(arr1, function(g) {
        return d.ms === g.ms;
      });
      return b;
    }));

    // sort the result
    result.sort(function (a, b) { return a.ms - b.ms; });
    return result;
  }

  function inAButNotInB(arr1, arr2) {
    return _.filter(arr1, function (d) {
      return !_.contains(arr2, d);
    });
  }

  // HELPER METHODS }}}

  //{{{ INITIALIZATION (runs once)
  // INITIALIZATION }}}

  //{{{ MY (runs whenever something changes)

  var my = function () {
  }

  // MY }}}

  //{{{ PUBLIC METHODS

  my.addRawData = function (rData) {
    // data must be in the following form: (example)
    // [ {val: value_point, ms: ms_since_epoch},
    //   {val: value_point, ms: ms_since_epoch},
    //   {etc...},
    // ],

    if (!bd.rawData.levels[0]) {
      bd.rawData.levels[0] = [];
    }

    for (var i in rData) {
      var dat = rData[i];
      if (_.find(bd.rawData.levels[0], function (d) { return d.ms === dat.ms; })) {
        // We already have that data point
      } else {
        // Add a new object to the bd level
        bd.rawData.levels[0].push({ms: dat.ms, val: dat.val});
      }
    }

    bd.rawData.levels[0].sort(function (a, b) { return a.ms - b.ms; });

    rebin();
    return my;
  }

  my.addBinnedData = function (bData) {
    // data must be in the form of the following example:
    // { average: {
    //     levels: [
    //       [{val: value_point, ms: ms_since_epoch},
    //        {val: value_point, ms: ms_since_epoch},
    //        {etc...}],
    //       [etc.]
    //     ],
    //   },
    //   q1: {
    //     levels: [
    //       [etc.]
    //     ],
    //   },
    //   etc: {},
    // }

    for (var key in bData) { // for each of max_val, min_val, etc.
      for (var lvl in bData[key].levels) { // for each level
        //if we don't have a level for this already, initialize one
        if (!bd[key].levels[lvl]) {
          bd[key].levels[lvl] = [];
        }

        bd[key].levels[lvl] = combineAndSortArraysOfDateValObjects(bd[key].levels[lvl], bData[key].levels[lvl]);
      } // for each received data point
    }; // for each of max_val, min_val, etc.

    rebin();
    return my;
  }

  my.haveDataInRange = function(ms_range, level) {
    // Determine the number of samples which we should have in the given range.

    if (level === 0) {
      key = "rawData";
    } else {
      key = "average";
    }

    var datedRange = my.getDateRange(key, level, ms_range);

    if (datedRange.length === 0) {
      return false;
    }

    var firstSample = datedRange[0].ms;
    var oneSample = 1000 / 200; // milliseconds per sample
    var sampleSize = Math.pow(2, level) * oneSample;

    if (firstSample > ms_range[0] + sampleSize) {
      return false;
    }

    var actualRange = ms_range[1] - firstSample;
    var numberWeShouldHave = Math.floor(actualRange / sampleSize);

    var numberWeHave = datedRange.length;

    return numberWeHave >= numberWeShouldHave;
  }

  my.missingBins = function(ms_range, level) {
    // TODO: Return which bins which we are missing in the given range and level.
    // TODO: returns [[start, end],[start,end],...] ranges of required data

    if (level === 0) {
      key = "rawData";
    } else {
      key = "average";
    }

    var datedRange = my.getDateRange(key, level, ms_range);

    if (datedRange.length === 0) {
      return [ms_range];
    }

    var firstSample = datedRange[0].ms;
    var oneSample = 1000 / 200; // milliseconds per sample
    var sampleSize = Math.pow(2, level) * oneSample;

    var howManyFitAbove = Math.floor((ms_range[1] - firstSample) / sampleSize);
    var howManyFitBelow = Math.floor((firstSample - ms_range[0]) / sampleSize);

    var whichSamplesWeShouldHaveForThisRange = [];

    // add first sample
    whichSamplesWeShouldHaveForThisRange.push(firstSample);

    // add above samples
    _.times(howManyFitAbove, function (i) {
      whichSamplesWeShouldHaveForThisRange.push(firstSample + ((i+1) * sampleSize));
    });

    // add below samples
    _.times(howManyFitBelow, function (i) {
      whichSamplesWeShouldHaveForThisRange.push(firstSample - ((i+1) * sampleSize));
    });

    var missingSamples = inAButNotInB(whichSamplesWeShouldHaveForThisRange, _.pluck(datedRange, 'ms'));

    var missingRanges = []; // form: [[0,1],[1,2],[4,5],[5,6],[6,7]]

    _.each(missingSamples, function (d,i) {
      missingRanges.push([d, d + sampleSize]);
      // missingRanges will now be like this: [[0,1],[1,2],[4,5],[5,6],[6,7]]
    });

    return missingRanges;
  }

  my.getMinRaw = function () {
    return d3.min(bd.rawData.levels[0], function(d) { return d.val; });
  }

  my.getMaxRaw = function () {
    return d3.max(bd.rawData.levels[0], function(d) { return d.val; });
  }

  my.getColor = function (key) {
    return bd[key].color;
  }

  my.getOpacity = function (key) {
    return bd[key].opacity;
  }

  my.getAllInRange = function(lvl, range) {
    // TODO TODO TODO
    // return a bd-like data structure but only
    // with data in the following range and level
    // from all keys

    // initialize the data structure to be sent
    var theKeys = ["average", "q1", "q3", "mins", "maxes"];
    var send_req = {};
    for (var i = 0; i < theKeys.length; i++) {
      send_req[theKeys[i]] = {};
      send_req[theKeys[i]].levels = [];
      send_req[theKeys[i]].levels[lvl] = my.getDateRange(theKeys[i], lvl, range);
    }
    //for(i=0;i<howManyPointsToGenerate;i++) {
    //  var val = randomPoint();
    //  var val_q1 = val - (Math.random() * 1.2);
    //  var val_q3 = val + (Math.random() * 1.2);
    //  var val_min = val_q1 - (Math.random() * 2);
    //  var val_max = val_q3 + (Math.random() * 2);
    //  var dat = parseInt(req.ms_start) - (parseInt(req.ms_start) % msPerBin) - 5; // TODO: magic hack

    //  send_req.average.levels[req.bin_level].push({ms: dat + (i * msPerBin), val: val});
    //  send_req.q1.levels[req.bin_level].push({ms: dat + (i * msPerBin), val: val_q1});
    //  send_req.q3.levels[req.bin_level].push({ms: dat + (i * msPerBin), val: val_q3});
    //  send_req.mins.levels[req.bin_level].push({ms: dat + (i * msPerBin), val: val_min});
    //  send_req.maxes.levels[req.bin_level].push({ms: dat + (i * msPerBin), val: val_max});
    //}
    return send_req;
  }

  my.getDateRange = function (key, lvl, range) {
    // filter an array so that we don't render much more
    // than the required amount of line and area
    return _.filter(bd[key].levels[lvl], function (d, i) {
      return d.ms <= range[1] && d.ms >= range[0];
    });
  }

  my.getKeys = function () {
    return bd.keys.slice(0); // give a copy of the array
  }

  my.bd = function () { // TODO: JUST FOR TESTING
    return bd;
  }

  // PUBLIC METHODS }}}

  return my;
}

/* vim: set foldmethod=marker: */
<|endoftext|>


<commit_before>/**
 * The authorization token with which calls to the API are made.
 * @type {String}
 */
let token = '';

/**
 * Authorize via OAuth 2.0 for Dropbox API calls.
 *
 * @parameter {string} clientId your app's key
 * @parameter {string} redirectUri the uri where the user should be redirected
 * to, after authorization has completed
 * @return {function} a promise that resolves or fails both with the returned
 * HTTP status code
 */
const authorize = (clientId, redirectUri = '') => {
  return new Promise((resolve, reject) => {
    reject('not implemented yet');
  });
};

/**
 * Set the token that is used for all Dropbox API calls to the given value.
 * @param  {string} t The new token value.
 */
const setToken = (t) => {
  if (typeof t !== 'string') {
    throw new Error('invalid argument ' + t + ' (expected: string)');
  }
  token = t;
};

module.exports = {
  authorize,
  setToken
};
<commit_msg>Return the authorization url when `authorize` fails<commit_after>/**
 * The authorization token with which calls to the API are made.
 * @type {String}
 */
let token = '';

const AUTHORIZE_ENDPOINT = 'https://www.dropbox.com/oauth2/authorize';

const getAuthorizationUrl = (clientId) => {
  return AUTHORIZE_ENDPOINT + '?' +
    'response_type=token' +
    'client_id=' + clientId;
};

/**
 * Authorize via OAuth 2.0 for Dropbox API calls.
 *
 * @parameter {string} clientId your app's key
 * @parameter {string} redirectUri the uri where the user should be redirected
 * to, after authorization has completed
 * @return {function} a promise that resolves or fails both with the returned
 * HTTP status code
 */
const authorize = (clientId, redirectUri = '') => {
  return new Promise((resolve, reject) => {
    reject('Not implemented yet, please obtain a token manually by calling ' +
      getAuthorizationUrl(clientId));
  });
};

/**
 * Set the token that is used for all Dropbox API calls to the given value.
 * @param  {string} t The new token value.
 */
const setToken = (t) => {
  if (typeof t !== 'string') {
    throw new Error('invalid argument ' + t + ' (expected: string)');
  }
  token = t;
};

module.exports = {
  authorize,
  setToken
};
<|endoftext|>


<commit_before>// Written by Michael Stepner, 8 April 2015.
//
/* The MIT License (MIT):
Copyright (c) 2015 Michael Stepner

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// ==UserScript==
// @name        Fastmail - Control folder badges
// @namespace   https://michaelstepner.com
// @description Turn badges on or off for specific folders, or show total # of messages.
// @include     https://www.fastmail.com/mail/*
// @version     0.0.9
// @updateURL   https://github.com/michaelstepner/fastmail-FolderBadgeControls/raw/master/Fastmail_-_Folder_Badge_Controls.user.js
// @require     https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_registerMenuCommand
// @grant       GM_addStyle
// ==/UserScript==

///// Retrieve configuration
var foldersShowAllOnBadge  = GM_getValue ("foldersShowAllOnBadge",  "");
var foldersAlwaysShowBadge = GM_getValue ("foldersAlwaysShowBadge", "");
var foldersNeverShowBadge  = GM_getValue ("foldersNeverShowBadge", "");
var firstConfig  = GM_getValue ("firstConfig", "");

///// Create a configuration form in a popup box

// Insert HTML for the popup box
$("body[contenteditable!='true']").append ( '                                                          \

                                               \
    <form>  \

Instructions:
 \

In each entry, list the folders whose badge you want to modify.
 \

Separate each folder name by a semicolon.
 \

You need to specify folders using the name in their URL:
 \

   https://www.fastmail.com/mail/Folder_Name/?u=0xx00000
 \

 \

Configuration:
 \

Show total number of messages (read and unread) on badge:
 \
        <input type="text" id="gmConfigShowTotal">                           \

Always show badge:
 \
        <input type="text" id="gmConfigShowAlways" value="">                           \

Never show badge:
 \
        <input type="text" id="gmConfigShowNever" value="">                           \

<button id="gmClosePopupAndCancel" type="button">Cancel</button> \
        <button id="gmClosePopupAndSave" type="button">Submit</button>         \
    </form>                                                                   \

                                                                    \
' );

// Detect whether running in Greasemonkey or Tampermonkey
var scriptEngine;
if (typeof GM_info === "undefined") {
    scriptEngine = "plain";
    // plain Chrome, or Opera, or scriptish, or Safari, or rarer
}
else {
    scriptEngine = GM_info.scriptHandler  ||  "Greasemonkey";
}

// Write correct instructions for Greasemonkey or Tampermonkey
if (scriptEngine=="Greasemonkey") {
    document.getElementById("gmMenuCommandInstructions").innerHTML = "You can always return to this configuration box via Tools >
   > Greasemonkey > User script commands...
   > Folder badge configuration";
}
else if (scriptEngine=="Tampermonkey") {
    document.getElementById("gmMenuCommandInstructions").innerHTML = "You can always return to this configuration box by clicking
   the Tampermonkey menu button while in Fastmail, then
   clicking 'Folder badge configuration'.";
}
else {
    document.getElementById("gmMenuCommandInstructions").innerHTML = "You seem to be running this userscript without Greasemonkey
   or Tampermonkey. Even if it is working, you will be unable to
   return to this configuration box later. You should use
   Greasemonkey in Firefox or Tampermonkey otherwise.";
}

// Hide the popup box unless config has never been done before
if (firstConfig=="done") {
    $("#gmPopupContainer").hide ();
}

// Set the input boxes to contain current config values
function setPopupConfigValues () {
    document.getElementById('gmConfigShowTotal').value=foldersShowAllOnBadge ;
    document.getElementById('gmConfigShowAlways').value=foldersAlwaysShowBadge ;
    document.getElementById('gmConfigShowNever').value=foldersNeverShowBadge ;
}
setPopupConfigValues ();

// Save the new config values on 'Submit'
$("#gmClosePopupAndSave").click ( function () {
    foldersShowAllOnBadge  = document.getElementById('gmConfigShowTotal').value ;
    foldersAlwaysShowBadge = document.getElementById('gmConfigShowAlways').value ;
    foldersNeverShowBadge  = document.getElementById('gmConfigShowNever').value ;
    updateConfig();
    updateSelectors();
    $("#gmPopupContainer").hide ();
    refreshCustomBadges(folderSelectors);
} );
function updateConfig () {
    GM_setValue ("foldersShowAllOnBadge", foldersShowAllOnBadge);
    GM_setValue ("foldersAlwaysShowBadge", foldersAlwaysShowBadge);
    GM_setValue ("foldersNeverShowBadge", foldersNeverShowBadge);
    GM_setValue ("firstConfig", "done");
}

// Restore popup text entry values on 'Cancel'
$("#gmClosePopupAndCancel").click ( function () {
    setPopupConfigValues ();
    $("#gmPopupContainer").hide ();
} );

// Style the config popup box with CSS
GM_addStyle ( "                                                 \
    #gmPopupContainer {                                         \
        position:               fixed;                          \
        top:                    20%;                            \
        left:                   20%;                            \
        padding:                1em 2em;                            \
        background:             #C0C0C0;                     \
        border:                 3px double black;               \
        border-radius:          1ex;                            \
        z-index:                777;                            \
    }                                                           \
    #gmPopupContainer button{                                   \
        cursor:                 pointer;                        \
        margin:                 1em 1em 0;                      \
        padding-top: 5px; \
        padding-right: 5px; \
        padding-bottom: 5px; \
        padding-left: 5px; \
        border:                 2px outset buttonface;          \
        margin-left: 75px; \
    }                                                           \
    #gmPopupContainer p{                                   \
        line-height: 20px;          \
    }                                                           \
    #gmPopupContainer input{                                   \
        width: 350px;           \
        margin-bottom: 10px; \
    }                                                           \
    #gmPopupContainer h1{                                   \
        font-weight: bold;          \
        line-height: 30px;          \
        padding-top: 0px; \
    }                                                           \
    #gmPopupContainer strong{                                   \
        font-weight: bold;          \
    }                                                           \
" );

///// Add Greasemonkey context menu command to edit configuration
GM_registerMenuCommand ("Folder badge configuration", changeConfig);
function changeConfig () {
    $("#gmPopupContainer").show ();
}

///////////////////////////////

///// Define selectors
function defineSelectorsFromFolders() {
    if (foldersShowAllOnBadge!="") {
        var selectorsShowAllOnBadge = "[href^='/mail/" + foldersShowAllOnBadge.split(";").join("/?u='],[href^='/mail/") + "/?u=']";
    } else {
        var selectorsShowAllOnBadge = null
    }

    if (foldersAlwaysShowBadge!="") {
        var selectorsAlwaysShowBadge = "[href^='/mail/" + foldersAlwaysShowBadge.split(";").join("/?u='],[href^='/mail/") + "/?u=']";
    } else {
        var selectorsAlwaysShowBadge = null
    }

    if (foldersNeverShowBadge!="") {
        var selectorsNeverShowBadge = "[href^='/mail/" + foldersNeverShowBadge.split(";").join("/?u='],[href^='/mail/") + "/?u=']";
    } else {
        var selectorsNeverShowBadge = null
    }

    return {
        showTotal: selectorsShowAllOnBadge,
        showAlways: selectorsAlwaysShowBadge,
        showNever: selectorsNeverShowBadge
    };
}
function updateSelectors() {
    folderSelectors = defineSelectorsFromFolders();
}

var folderSelectors = null;
updateSelectors();

///// Functions that control badges
function showAllOnBadge (jNode) {
    // Display badge with total number of messages (read and unread).
    var nMessagesStr = jNode.prop('title');
    var nMessages = nMessagesStr.replace("This folder is empty.","0").replace(/conversation[s]*/, "");
    jNode.children(".v-FolderSource-badge").text(nMessages);
    if (nMessages!=0) {
        jNode.children(".v-FolderSource-badge").removeClass("u-hidden");
    } else {
        jNode.children(".v-FolderSource-badge").addClass("u-hidden");
    }
}
function alwaysShowBadge (jNode) {
    // Always display badge, even if it is 0.
    jNode.children(".v-FolderSource-badge").removeClass("u-hidden");
}
function neverShowBadge (jNode) {
    // Never display badge.
    jNode.children(".v-FolderSource-badge").addClass("u-hidden");
}
function refreshCustomBadges (selectors) {
    $(selectors.showTotal).each(function() {
        showAllOnBadge ($(this));
    });
    $(selectors.showAlways).each(function() {
        alwaysShowBadge ($(this));
    });
    $(selectors.showNever).each(function() {
        neverShowBadge ($(this));
    });
}

///// Mutation Observer
var targetNodes         = $("*"); // Note: there is certainly a narrower observer that could be set. If you figure it out, let me know!
var myObserver          = new MutationObserver (mutationHandler);
var obsConfig           = { childList: false, characterData: false, attributes: true, subtree: true, attributeFilter:['class'] };

//--- Add a target node to the observer. Can only add one node at a time.
targetNodes.each ( function () {
    myObserver.observe (this, obsConfig);
} );

function mutationHandler (mutationRecords) {
    mutationRecords.forEach ( function (mutation) {
        if(mutation.target.classList.contains("v-FolderSource")) {
            //console.log (mutation.target);
            refreshCustomBadges(folderSelectors);
        }
    } );
}

<commit_msg>Add @author to metadata<commit_after>// Written by Michael Stepner, 8 April 2015.
//
/* The MIT License (MIT):
Copyright (c) 2015 Michael Stepner

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// ==UserScript==
// @name        Fastmail - Control folder badges
// @namespace   https://michaelstepner.com
// @author      Michael Stepner
// @description Turn badges on or off for specific folders, or show total # of messages.
// @include     https://www.fastmail.com/mail/*
// @version     0.0.9
// @updateURL   https://github.com/michaelstepner/fastmail-FolderBadgeControls/raw/master/Fastmail_-_Folder_Badge_Controls.user.js
// @require     https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_registerMenuCommand
// @grant       GM_addStyle
// ==/UserScript==

///// Retrieve configuration
var foldersShowAllOnBadge  = GM_getValue ("foldersShowAllOnBadge",  "");
var foldersAlwaysShowBadge = GM_getValue ("foldersAlwaysShowBadge", "");
var foldersNeverShowBadge  = GM_getValue ("foldersNeverShowBadge", "");
var firstConfig  = GM_getValue ("firstConfig", "");

///// Create a configuration form in a popup box

// Insert HTML for the popup box
$("body[contenteditable!='true']").append ( '                                                          \

                                               \
    <form>  \

Instructions:
 \

In each entry, list the folders whose badge you want to modify.
 \

Separate each folder name by a semicolon.
 \

You need to specify folders using the name in their URL:
 \

   https://www.fastmail.com/mail/Folder_Name/?u=0xx00000
 \

 \

Configuration:
 \

Show total number of messages (read and unread) on badge:
 \
        <input type="text" id="gmConfigShowTotal">                           \

Always show badge:
 \
        <input type="text" id="gmConfigShowAlways" value="">                           \

Never show badge:
 \
        <input type="text" id="gmConfigShowNever" value="">                           \

<button id="gmClosePopupAndCancel" type="button">Cancel</button> \
        <button id="gmClosePopupAndSave" type="button">Submit</button>         \
    </form>                                                                   \

                                                                    \
' );

// Detect whether running in Greasemonkey or Tampermonkey
var scriptEngine;
if (typeof GM_info === "undefined") {
    scriptEngine = "plain";
    // plain Chrome, or Opera, or scriptish, or Safari, or rarer
}
else {
    scriptEngine = GM_info.scriptHandler  ||  "Greasemonkey";
}

// Write correct instructions for Greasemonkey or Tampermonkey
if (scriptEngine=="Greasemonkey") {
    document.getElementById("gmMenuCommandInstructions").innerHTML = "You can always return to this configuration box via Tools >
   > Greasemonkey > User script commands...
   > Folder badge configuration";
}
else if (scriptEngine=="Tampermonkey") {
    document.getElementById("gmMenuCommandInstructions").innerHTML = "You can always return to this configuration box by clicking
   the Tampermonkey menu button while in Fastmail, then
   clicking 'Folder badge configuration'.";
}
else {
    document.getElementById("gmMenuCommandInstructions").innerHTML = "You seem to be running this userscript without Greasemonkey
   or Tampermonkey. Even if it is working, you will be unable to
   return to this configuration box later. You should use
   Greasemonkey in Firefox or Tampermonkey otherwise.";
}

// Hide the popup box unless config has never been done before
if (firstConfig=="done") {
    $("#gmPopupContainer").hide ();
}

// Set the input boxes to contain current config values
function setPopupConfigValues () {
    document.getElementById('gmConfigShowTotal').value=foldersShowAllOnBadge ;
    document.getElementById('gmConfigShowAlways').value=foldersAlwaysShowBadge ;
    document.getElementById('gmConfigShowNever').value=foldersNeverShowBadge ;
}
setPopupConfigValues ();

// Save the new config values on 'Submit'
$("#gmClosePopupAndSave").click ( function () {
    foldersShowAllOnBadge  = document.getElementById('gmConfigShowTotal').value ;
    foldersAlwaysShowBadge = document.getElementById('gmConfigShowAlways').value ;
    foldersNeverShowBadge  = document.getElementById('gmConfigShowNever').value ;
    updateConfig();
    updateSelectors();
    $("#gmPopupContainer").hide ();
    refreshCustomBadges(folderSelectors);
} );
function updateConfig () {
    GM_setValue ("foldersShowAllOnBadge", foldersShowAllOnBadge);
    GM_setValue ("foldersAlwaysShowBadge", foldersAlwaysShowBadge);
    GM_setValue ("foldersNeverShowBadge", foldersNeverShowBadge);
    GM_setValue ("firstConfig", "done");
}

// Restore popup text entry values on 'Cancel'
$("#gmClosePopupAndCancel").click ( function () {
    setPopupConfigValues ();
    $("#gmPopupContainer").hide ();
} );

// Style the config popup box with CSS
GM_addStyle ( "                                                 \
    #gmPopupContainer {                                         \
        position:               fixed;                          \
        top:                    20%;                            \
        left:                   20%;                            \
        padding:                1em 2em;                            \
        background:             #C0C0C0;                     \
        border:                 3px double black;               \
        border-radius:          1ex;                            \
        z-index:                777;                            \
    }                                                           \
    #gmPopupContainer button{                                   \
        cursor:                 pointer;                        \
        margin:                 1em 1em 0;                      \
        padding-top: 5px; \
        padding-right: 5px; \
        padding-bottom: 5px; \
        padding-left: 5px; \
        border:                 2px outset buttonface;          \
        margin-left: 75px; \
    }                                                           \
    #gmPopupContainer p{                                   \
        line-height: 20px;          \
    }                                                           \
    #gmPopupContainer input{                                   \
        width: 350px;           \
        margin-bottom: 10px; \
    }                                                           \
    #gmPopupContainer h1{                                   \
        font-weight: bold;          \
        line-height: 30px;          \
        padding-top: 0px; \
    }                                                           \
    #gmPopupContainer strong{                                   \
        font-weight: bold;          \
    }                                                           \
" );

///// Add Greasemonkey context menu command to edit configuration
GM_registerMenuCommand ("Folder badge configuration", changeConfig);
function changeConfig () {
    $("#gmPopupContainer").show ();
}

///////////////////////////////

///// Define selectors
function defineSelectorsFromFolders() {
    if (foldersShowAllOnBadge!="") {
        var selectorsShowAllOnBadge = "[href^='/mail/" + foldersShowAllOnBadge.split(";").join("/?u='],[href^='/mail/") + "/?u=']";
    } else {
        var selectorsShowAllOnBadge = null
    }

    if (foldersAlwaysShowBadge!="") {
        var selectorsAlwaysShowBadge = "[href^='/mail/" + foldersAlwaysShowBadge.split(";").join("/?u='],[href^='/mail/") + "/?u=']";
    } else {
        var selectorsAlwaysShowBadge = null
    }

    if (foldersNeverShowBadge!="") {
        var selectorsNeverShowBadge = "[href^='/mail/" + foldersNeverShowBadge.split(";").join("/?u='],[href^='/mail/") + "/?u=']";
    } else {
        var selectorsNeverShowBadge = null
    }

    return {
        showTotal: selectorsShowAllOnBadge,
        showAlways: selectorsAlwaysShowBadge,
        showNever: selectorsNeverShowBadge
    };
}
function updateSelectors() {
    folderSelectors = defineSelectorsFromFolders();
}

var folderSelectors = null;
updateSelectors();

///// Functions that control badges
function showAllOnBadge (jNode) {
    // Display badge with total number of messages (read and unread).
    var nMessagesStr = jNode.prop('title');
    var nMessages = nMessagesStr.replace("This folder is empty.","0").replace(/conversation[s]*/, "");
    jNode.children(".v-FolderSource-badge").text(nMessages);
    if (nMessages!=0) {
        jNode.children(".v-FolderSource-badge").removeClass("u-hidden");
    } else {
        jNode.children(".v-FolderSource-badge").addClass("u-hidden");
    }
}
function alwaysShowBadge (jNode) {
    // Always display badge, even if it is 0.
    jNode.children(".v-FolderSource-badge").removeClass("u-hidden");
}
function neverShowBadge (jNode) {
    // Never display badge.
    jNode.children(".v-FolderSource-badge").addClass("u-hidden");
}
function refreshCustomBadges (selectors) {
    $(selectors.showTotal).each(function() {
        showAllOnBadge ($(this));
    });
    $(selectors.showAlways).each(function() {
        alwaysShowBadge ($(this));
    });
    $(selectors.showNever).each(function() {
        neverShowBadge ($(this));
    });
}

///// Mutation Observer
var targetNodes         = $("*"); // Note: there is certainly a narrower observer that could be set. If you figure it out, let me know!
var myObserver          = new MutationObserver (mutationHandler);
var obsConfig           = { childList: false, characterData: false, attributes: true, subtree: true, attributeFilter:['class'] };

//--- Add a target node to the observer. Can only add one node at a time.
targetNodes.each ( function () {
    myObserver.observe (this, obsConfig);
} );

function mutationHandler (mutationRecords) {
    mutationRecords.forEach ( function (mutation) {
        if(mutation.target.classList.contains("v-FolderSource")) {
            //console.log (mutation.target);
            refreshCustomBadges(folderSelectors);
        }
    } );
}

<|endoftext|>


<commit_before>'use strict';

var grunt = require('grunt');

var BOWER_PATH = '../react-bower/';
var BOWER_GLOB = [BOWER_PATH + '*.{js}'];
var BOWER_FILES = [
  'react.js',
  'react.min.js',
  'react-with-addons.js',
  'react-with-addons.min.js',
  'react-dom.js',
  'react-dom.min.js',
  'react-dom-server.js',
  'react-dom-server.min.js',
];

var EXAMPLES_PATH = 'examples/';
var EXAMPLES_GLOB = [EXAMPLES_PATH + '**/*.*'];

var STARTER_PATH = 'starter/';
var STARTER_GLOB = [STARTER_PATH + '/**/*.*'];

var STARTER_BUILD_PATH = 'build/starter/';

var JS_PATH = 'build/';
var JS_GLOB = [JS_PATH + '/*.js'];

var VERSION;
var VERSION_STRING;

function _gitCommitAndTag(cwd, commitMsg, tag, cb) {
  // `git add *` to make sure we catch untracked files
  // `git add -u` to make sure we remove deleted files
  // `git commit -m {commitMsg}`
  // `git tag -a {tag}`
  var opts = {cwd: cwd};
  var gitAddAll = {
    cmd: 'git',
    args: ['add', '*'],
    opts: opts,
  };
  var gitAddDel = {
    cmd: 'git',
    args: ['add', '-u'],
    opts: opts,
  };
  var gitCommit = {
    cmd: 'git',
    args: ['commit', '-m', commitMsg],
    opts: opts,
  };
  var gitTag = {
    cmd: 'git',
    args: ['tag', tag],
    opts: opts,
  };
  grunt.util.spawn(gitAddAll, function() {
    grunt.util.spawn(gitAddDel, function() {
      grunt.util.spawn(gitCommit, function() {
        if (tag) {
          grunt.util.spawn(gitTag, cb);
        } else {
          cb();
        }
      });
    });
  });
}

function setup() {
  if (!grunt.file.exists(BOWER_PATH)) {
    grunt.log.error('Make sure you have the react-bower repository checked ' +
                    'out at ../react-bower');
    return false;
  }

  VERSION = grunt.config.data.pkg.version;
  VERSION_STRING = 'v' + VERSION;
}

function bower() {
  var done = this.async();

  // clean out the bower folder in case we're removing files
  var files = grunt.file.expand(BOWER_GLOB);
  files.forEach(function(file) {
    grunt.file.delete(file, {force: true});
  });

  // Now copy over build files
  BOWER_FILES.forEach(function(file) {
    grunt.file.copy('build/' + file, BOWER_PATH + file);
  });

  // Commit and tag the repo
  _gitCommitAndTag(BOWER_PATH, VERSION_STRING, VERSION_STRING, done);
}

function docs() {
  grunt.file.copy('build/react-' + VERSION + '.zip', 'docs/downloads/react-' + VERSION + '.zip');
  grunt.file.copy('build/react.js', 'docs/js/react.js');
  grunt.file.copy('build/react-dom.js', 'docs/js/react-dom.js');
}

function msg() {
  // Just output a friendly reminder message for the rest of the process
  grunt.log.subhead('Release *almost* complete...');
  var steps = [
    'Still todo:',
    '* put files on CDN',
    '* add starter pack (git add -f docs/downloads/react-version.zip)',
    '* push changes to git repositories',
    '* update docs branch variable in Travis CI',
    '* publish npm modules',
    '* announce it on FB/Twitter/mailing list',
  ];
  steps.forEach(function(ln) {
    grunt.log.writeln(ln);
  });
}

function starter() {
  // Copy over examples/ to build/starter/examples/
  // and starter/ to build/starter/

  grunt.file.expand(EXAMPLES_GLOB).forEach(function(file) {
    grunt.file.copy(
      file,
      STARTER_BUILD_PATH + file
    );
  });

  grunt.file.expand(STARTER_GLOB).forEach(function(file) {
    grunt.file.copy(
      file,
      'build/' + file
    );
  });

  grunt.file.expand(JS_GLOB).forEach(function(file) {
    grunt.file.copy(
      file,
      STARTER_BUILD_PATH + file
    );
  });
}

module.exports = {
  setup: setup,
  bower: bower,
  docs: docs,
  msg: msg,
  starter: starter,
};
<commit_msg>Ensure babelrc file gets packaged for starter kit (#6617) (cherry picked from commit 096e5c7cf77acb8deca389b4e3a7bb210744542f)<commit_after>'use strict';

var grunt = require('grunt');

var BOWER_PATH = '../react-bower/';
var BOWER_GLOB = [BOWER_PATH + '*.{js}'];
var BOWER_FILES = [
  'react.js',
  'react.min.js',
  'react-with-addons.js',
  'react-with-addons.min.js',
  'react-dom.js',
  'react-dom.min.js',
  'react-dom-server.js',
  'react-dom-server.min.js',
];

var EXAMPLES_PATH = 'examples/';
var EXAMPLES_GLOB = [EXAMPLES_PATH + '**/*.*', EXAMPLES_PATH + '**/.babelrc'];

var STARTER_PATH = 'starter/';
var STARTER_GLOB = [STARTER_PATH + '/**/*.*'];

var STARTER_BUILD_PATH = 'build/starter/';

var JS_PATH = 'build/';
var JS_GLOB = [JS_PATH + '/*.js'];

var VERSION;
var VERSION_STRING;

function _gitCommitAndTag(cwd, commitMsg, tag, cb) {
  // `git add *` to make sure we catch untracked files
  // `git add -u` to make sure we remove deleted files
  // `git commit -m {commitMsg}`
  // `git tag -a {tag}`
  var opts = {cwd: cwd};
  var gitAddAll = {
    cmd: 'git',
    args: ['add', '*'],
    opts: opts,
  };
  var gitAddDel = {
    cmd: 'git',
    args: ['add', '-u'],
    opts: opts,
  };
  var gitCommit = {
    cmd: 'git',
    args: ['commit', '-m', commitMsg],
    opts: opts,
  };
  var gitTag = {
    cmd: 'git',
    args: ['tag', tag],
    opts: opts,
  };
  grunt.util.spawn(gitAddAll, function() {
    grunt.util.spawn(gitAddDel, function() {
      grunt.util.spawn(gitCommit, function() {
        if (tag) {
          grunt.util.spawn(gitTag, cb);
        } else {
          cb();
        }
      });
    });
  });
}

function setup() {
  if (!grunt.file.exists(BOWER_PATH)) {
    grunt.log.error('Make sure you have the react-bower repository checked ' +
                    'out at ../react-bower');
    return false;
  }

  VERSION = grunt.config.data.pkg.version;
  VERSION_STRING = 'v' + VERSION;
}

function bower() {
  var done = this.async();

  // clean out the bower folder in case we're removing files
  var files = grunt.file.expand(BOWER_GLOB);
  files.forEach(function(file) {
    grunt.file.delete(file, {force: true});
  });

  // Now copy over build files
  BOWER_FILES.forEach(function(file) {
    grunt.file.copy('build/' + file, BOWER_PATH + file);
  });

  // Commit and tag the repo
  _gitCommitAndTag(BOWER_PATH, VERSION_STRING, VERSION_STRING, done);
}

function docs() {
  grunt.file.copy('build/react-' + VERSION + '.zip', 'docs/downloads/react-' + VERSION + '.zip');
  grunt.file.copy('build/react.js', 'docs/js/react.js');
  grunt.file.copy('build/react-dom.js', 'docs/js/react-dom.js');
}

function msg() {
  // Just output a friendly reminder message for the rest of the process
  grunt.log.subhead('Release *almost* complete...');
  var steps = [
    'Still todo:',
    '* put files on CDN',
    '* add starter pack (git add -f docs/downloads/react-version.zip)',
    '* push changes to git repositories',
    '* update docs branch variable in Travis CI',
    '* publish npm modules',
    '* announce it on FB/Twitter/mailing list',
  ];
  steps.forEach(function(ln) {
    grunt.log.writeln(ln);
  });
}

function starter() {
  // Copy over examples/ to build/starter/examples/
  // and starter/ to build/starter/

  grunt.file.expand(EXAMPLES_GLOB).forEach(function(file) {
    grunt.file.copy(
      file,
      STARTER_BUILD_PATH + file
    );
  });

  grunt.file.expand(STARTER_GLOB).forEach(function(file) {
    grunt.file.copy(
      file,
      'build/' + file
    );
  });

  grunt.file.expand(JS_GLOB).forEach(function(file) {
    grunt.file.copy(
      file,
      STARTER_BUILD_PATH + file
    );
  });
}

module.exports = {
  setup: setup,
  bower: bower,
  docs: docs,
  msg: msg,
  starter: starter,
};
<|endoftext|>


<commit_before>"use strict";

class gsuiPopup {
	static alert( title, msg ) {
		gsuiPopup._init();
		gsuiPopup._emptyCnt();
		gsuiPopup.clWindow.add( "gsui-notext", "gsui-nocancel" );
		return gsuiPopup._open( "alert", title, msg );
	}
	static confirm( title, msg ) {
		gsuiPopup._init();
		gsuiPopup._emptyCnt();
		gsuiPopup.clWindow.remove( "gsui-nocancel" );
		gsuiPopup.clWindow.add( "gsui-notext" );
		return gsuiPopup._open( "confirm", title, msg );
	}
	static prompt( title, msg, val ) {
		gsuiPopup._init();
		gsuiPopup._emptyCnt();
		gsuiPopup.clWindow.remove( "gsui-notext", "gsui-nocancel" );
		return gsuiPopup._open( "prompt", title, msg, val );
	}
	static custom( obj ) {
		gsuiPopup._init();
		gsuiPopup._emptyCnt();
		gsuiPopup._fnSubmit = obj.submit;
		gsuiPopup.clWindow.remove( "gsui-notext" );
		gsuiPopup.clWindow.toggle( "gsui-nocancel", !obj.showCancel );
		obj.element
			? gsuiPopup.elCnt.append( obj.element )
			: Element.prototype.append.apply( gsuiPopup.elCnt, obj.elements );
		return gsuiPopup._open( "custom", obj.title );
	}
	static close() {
		if ( gsuiPopup.isOpen ) {
			gsuiPopup.elCancel.click();
		}
	}

	// private:
	static _emptyCnt() {
		const elCnt = gsuiPopup.elCnt;

		while ( elCnt.firstChild ) {
			elCnt.firstChild.remove();
		}
	}
	static _init() {
		const that = gsuiPopup;

		if ( !that._ready ) {
			const el = that.elRoot = document.getElementById( "gsuiPopup" );

			that.elRoot = el;
			that.elOk = el.querySelector( "#gsuipp-inpOk" );
			that.elCnt = el.querySelector( "#gsuipp-cnt" );
			that.elMsg = el.querySelector( "#gsuipp-msg" );
			that.elText = el.querySelector( "#gsuipp-inpText" );
			that.elForm = el.querySelector( "form" );
			that.elWindow = el.querySelector( "#gsuipp-window" );
			that.elHeader = el.querySelector( "#gsuipp-head" );
			that.elCancel = el.querySelector( "#gsuipp-inpCancel" );
			that.clWindow = that.elWindow.classList;

			el.onclick =
			that.elCancel.onclick = that._cancelClick;
			that.elForm.onsubmit = that._submit;
			that.elWindow.onkeyup =
			that.elWindow.onclick = e => {
				e.stopPropagation();
			};
			that.elWindow.onkeydown = e => {
				if ( e.keyCode === 27 ) {
					that._cancelClick();
				}
				e.stopPropagation();
			};
			that._ready = true;
		}
	}
	static _open( type, title, msg, value ) {
		const that = gsuiPopup;

		that.type = type;
		that.isOpen = true;
		that.elHeader.textContent = title;
		that.elMsg.innerHTML = msg || "";
		that.elText.value = arguments.length > 3 ? value : "";
		that.elWindow.dataset.gsuiType =
		that.elRoot.classList.add( "gsui-show" );
		setTimeout( () => {
			if ( type === "prompt" ) {
				that.elText.select();
			} else {
				const inp = type !== "custom" ? null
					: that.elCnt.querySelector( "input" );

				( inp || that.elOk ).focus();
			}
		}, 250 );
		return new Promise( res => that.resolve = res )
			.then( val => {
				that.isOpen = false;
				that.elRoot.classList.remove( "gsui-show" );
				return val;
			} );
	}
	static _cancelClick() {
		gsuiPopup.resolve(
			gsuiPopup.type === "confirm" ? false :
			gsuiPopup.type === "prompt" ? null : undefined );
	}
	static _submit( e ) {
		if ( gsuiPopup.type === "custom" && gsuiPopup._fnSubmit ) {
			gsuiPopup._fnSubmit( e );
		}
		gsuiPopup.resolve(
			gsuiPopup.type === "confirm" ? true :
			gsuiPopup.type === "prompt" ? gsuiPopup.elText.value : undefined );
		return false;
	}
}
<commit_msg>gsuiPopup: .alert/confirm/prompt() takes optional arguments to handle specific ok/cancel msg<commit_after>"use strict";

class gsuiPopup {
	static alert( title, msg, ok ) {
		gsuiPopup._init();
		gsuiPopup._emptyCnt();
		gsuiPopup.clWindow.add( "gsui-notext", "gsui-nocancel" );
		gsuiPopup._setOkCancelBtns( ok, false );
		return gsuiPopup._open( "alert", title, msg );
	}
	static confirm( title, msg, ok, cancel ) {
		gsuiPopup._init();
		gsuiPopup._emptyCnt();
		gsuiPopup.clWindow.remove( "gsui-nocancel" );
		gsuiPopup.clWindow.add( "gsui-notext" );
		gsuiPopup._setOkCancelBtns( ok, cancel );
		return gsuiPopup._open( "confirm", title, msg );
	}
	static prompt( title, msg, val, ok, cancel ) {
		gsuiPopup._init();
		gsuiPopup._emptyCnt();
		gsuiPopup.clWindow.remove( "gsui-notext", "gsui-nocancel" );
		gsuiPopup._setOkCancelBtns( ok, cancel );
		return gsuiPopup._open( "prompt", title, msg, val );
	}
	static custom( obj ) {
		gsuiPopup._init();
		gsuiPopup._emptyCnt();
		gsuiPopup._fnSubmit = obj.submit;
		gsuiPopup.clWindow.remove( "gsui-notext" );
		gsuiPopup._setOkCancelBtns( obj.ok, obj.cancel || false );
		obj.element
			? gsuiPopup.elCnt.append( obj.element )
			: Element.prototype.append.apply( gsuiPopup.elCnt, obj.elements );
		return gsuiPopup._open( "custom", obj.title );
	}
	static close() {
		if ( gsuiPopup.isOpen ) {
			gsuiPopup.elCancel.click();
		}
	}

	// private:
	static _setOkCancelBtns( ok, cancel ) {
		gsuiPopup.clWindow.toggle( "gsui-nocancel", cancel === false );
		gsuiPopup.elCancel.value = cancel || "Cancel";
		gsuiPopup.elOk.value = ok || "Ok";
	}
	static _emptyCnt() {
		const elCnt = gsuiPopup.elCnt;

		while ( elCnt.firstChild ) {
			elCnt.firstChild.remove();
		}
	}
	static _init() {
		const that = gsuiPopup;

		if ( !that._ready ) {
			const el = that.elRoot = document.getElementById( "gsuiPopup" );

			that.elRoot = el;
			that.elOk = el.querySelector( "#gsuipp-inpOk" );
			that.elCnt = el.querySelector( "#gsuipp-cnt" );
			that.elMsg = el.querySelector( "#gsuipp-msg" );
			that.elText = el.querySelector( "#gsuipp-inpText" );
			that.elForm = el.querySelector( "form" );
			that.elWindow = el.querySelector( "#gsuipp-window" );
			that.elHeader = el.querySelector( "#gsuipp-head" );
			that.elCancel = el.querySelector( "#gsuipp-inpCancel" );
			that.clWindow = that.elWindow.classList;

			el.onclick =
			that.elCancel.onclick = that._cancelClick;
			that.elForm.onsubmit = that._submit;
			that.elWindow.onkeyup =
			that.elWindow.onclick = e => {
				e.stopPropagation();
			};
			that.elWindow.onkeydown = e => {
				if ( e.keyCode === 27 ) {
					that._cancelClick();
				}
				e.stopPropagation();
			};
			that._ready = true;
		}
	}
	static _open( type, title, msg, value ) {
		const that = gsuiPopup;

		that.type = type;
		that.isOpen = true;
		that.elHeader.textContent = title;
		that.elMsg.innerHTML = msg || "";
		that.elText.value = arguments.length > 3 ? value : "";
		that.elWindow.dataset.gsuiType =
		that.elRoot.classList.add( "gsui-show" );
		setTimeout( () => {
			if ( type === "prompt" ) {
				that.elText.select();
			} else {
				const inp = type !== "custom" ? null
					: that.elCnt.querySelector( "input" );

				( inp || that.elOk ).focus();
			}
		}, 250 );
		return new Promise( res => that.resolve = res )
			.then( val => {
				that.isOpen = false;
				that.elRoot.classList.remove( "gsui-show" );
				return val;
			} );
	}
	static _cancelClick() {
		gsuiPopup.resolve(
			gsuiPopup.type === "confirm" ? false :
			gsuiPopup.type === "prompt" ? null : undefined );
	}
	static _submit( e ) {
		if ( gsuiPopup.type === "custom" && gsuiPopup._fnSubmit ) {
			gsuiPopup._fnSubmit( e );
		}
		gsuiPopup.resolve(
			gsuiPopup.type === "confirm" ? true :
			gsuiPopup.type === "prompt" ? gsuiPopup.elText.value : undefined );
		return false;
	}
}
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react'
import {Scrollbars} from 'react-custom-scrollbars'

class FancyScrollbox extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    const {children, className} = this.props

    return (
      <Scrollbars
        className={`fancy-scroll--container ${className}`}
        autoHide={true}
        autoHideTimeout={1000}
        autoHideDuration={250}
        renderTrackHorizontal={props => <div {...props} className="fancy-scroll--track-h"/>}
        renderTrackVertical={props => <div {...props} className="fancy-scroll--track-v"/>}
        renderThumbHorizontal={props => <div {...props} className="fancy-scroll--thumb-h"/>}
        renderThumbVertical={props => <div {...props} className="fancy-scroll--thumb-v"/>}
      >
        {children}
      </Scrollbars>
    )
  }
}

const {node, string} = PropTypes

FancyScrollbox.propTypes = {
  children: node.isRequired,
  className: string.isRequired,
}

export default FancyScrollbox
<commit_msg>Add autoHide prop to pass down<commit_after>import React, {Component, PropTypes} from 'react'
import classnames from 'classnames'
import {Scrollbars} from 'react-custom-scrollbars'

class FancyScrollbox extends Component {
  constructor(props) {
    super(props)
  }

  static defaultProps = {
    autoHide: true,
  }

  render() {
    const {autoHide, children, className} = this.props

    return (
      <Scrollbars
        className={classnames('fancy-scroll--container', {[className]: className})}
        autoHide={autoHide}
        autoHideTimeout={1000}
        autoHideDuration={250}
        renderTrackHorizontal={props => <div {...props} className="fancy-scroll--track-h"/>}
        renderTrackVertical={props => <div {...props} className="fancy-scroll--track-v"/>}
        renderThumbHorizontal={props => <div {...props} className="fancy-scroll--thumb-h"/>}
        renderThumbVertical={props => <div {...props} className="fancy-scroll--thumb-v"/>}
      >
        {children}
      </Scrollbars>
    )
  }
}

const {bool, node, string} = PropTypes

FancyScrollbox.propTypes = {
  children: node.isRequired,
  className: string,
  autoHide: bool,
}

export default FancyScrollbox<|endoftext|>


<commit_before>import React, {PropTypes, Component} from 'react'
import {Link} from 'react-router'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'

import Authorized, {ADMIN_ROLE, SUPERADMIN_ROLE} from 'src/auth/Authorized'

import classnames from 'classnames'

import {meChangeOrganizationAsync} from 'shared/actions/auth'

class UserNavBlock extends Component {
  constructor(props) {
    super(props)
  }

  handleChangeCurrentOrganization = organizationID => () => {
    const {links, meChangeOrganization} = this.props
    meChangeOrganization(links.me, {organization: organizationID})
  }

  render() {
    const {
      logoutLink,
      links: {external: {custom: customLinks}},
      me,
      me: {currentOrganization, organizations, roles},
      sourcePrefix,
    } = this.props

    // TODO: find a better way to glean this information.
    // Need this method for when a user is a superadmin,
    // which doesn't reflect their role in the current org
    const currentRole = roles.find(cr => {
      return cr.organization === currentOrganization.id
    }).name

    return (

            {currentOrganization.name} ({currentRole})

            {me.name}

          <Authorized requiredRole={SUPERADMIN_ROLE}>
            <Link
              className="sidebar-menu--item"
              to={`${sourcePrefix}/organizations`}
            >
              Manage Organizations
            </Link>
          </Authorized>
          <Authorized requiredRole={ADMIN_ROLE}>
            <Link className="sidebar-menu--item" to={`${sourcePrefix}/users`}>
              Manage Users
            </Link>
          </Authorized>

            Logout

Switch Organizations

          {roles.map((r, i) => {
            const isLinkCurrentOrg = currentOrganization.id === r.organization
            return (

                {organizations.find(o => o.id === r.organization).name}{' '}
                ({r.name})

            )
          })}
          {customLinks
            ?
Custom Links

            : null}
          {customLinks
            ? customLinks.map((link, i) =>

                  {link.name}

              )
            : null}

    )
  }
}

const {arrayOf, func, shape, string} = PropTypes

UserNavBlock.propTypes = {
  links: shape({
    me: string,
    external: shape({
      custom: arrayOf(
        shape({
          name: string.isRequired,
          url: string.isRequired,
        })
      ),
    }),
  }),
  logoutLink: string.isRequired,
  me: shape({
    currentOrganization: shape({
      id: string.isRequired,
      name: string.isRequired,
    }),
    name: string,
    organizations: arrayOf(
      shape({
        id: string.isRequired,
        name: string.isRequired,
      })
    ),
    roles: arrayOf(
      shape({
        id: string,
        name: string,
      })
    ),
    role: string,
  }).isRequired,
  meChangeOrganization: func.isRequired,
  sourcePrefix: string.isRequired, // TODO: remove this prop once we move /users & /organizations above sources
}

const mapDispatchToProps = dispatch => ({
  meChangeOrganization: bindActionCreators(meChangeOrganizationAsync, dispatch),
})

export default connect(null, mapDispatchToProps)(UserNavBlock)
<commit_msg>Remove unnecessary constructor method in UserNavBlock<commit_after>import React, {PropTypes, Component} from 'react'
import {Link} from 'react-router'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'

import Authorized, {ADMIN_ROLE, SUPERADMIN_ROLE} from 'src/auth/Authorized'

import classnames from 'classnames'

import {meChangeOrganizationAsync} from 'shared/actions/auth'

class UserNavBlock extends Component {
  handleChangeCurrentOrganization = organizationID => () => {
    const {links, meChangeOrganization} = this.props
    meChangeOrganization(links.me, {organization: organizationID})
  }

  render() {
    const {
      logoutLink,
      links: {external: {custom: customLinks}},
      me,
      me: {currentOrganization, organizations, roles},
      sourcePrefix,
    } = this.props

    // TODO: find a better way to glean this information.
    // Need this method for when a user is a superadmin,
    // which doesn't reflect their role in the current org
    const currentRole = roles.find(cr => {
      return cr.organization === currentOrganization.id
    }).name

    return (

            {currentOrganization.name} ({currentRole})

            {me.name}

          <Authorized requiredRole={SUPERADMIN_ROLE}>
            <Link
              className="sidebar-menu--item"
              to={`${sourcePrefix}/organizations`}
            >
              Manage Organizations
            </Link>
          </Authorized>
          <Authorized requiredRole={ADMIN_ROLE}>
            <Link className="sidebar-menu--item" to={`${sourcePrefix}/users`}>
              Manage Users
            </Link>
          </Authorized>

            Logout

Switch Organizations

          {roles.map((r, i) => {
            const isLinkCurrentOrg = currentOrganization.id === r.organization
            return (

                {organizations.find(o => o.id === r.organization).name}{' '}
                ({r.name})

            )
          })}
          {customLinks
            ?
Custom Links

            : null}
          {customLinks
            ? customLinks.map((link, i) =>

                  {link.name}

              )
            : null}

    )
  }
}

const {arrayOf, func, shape, string} = PropTypes

UserNavBlock.propTypes = {
  links: shape({
    me: string,
    external: shape({
      custom: arrayOf(
        shape({
          name: string.isRequired,
          url: string.isRequired,
        })
      ),
    }),
  }),
  logoutLink: string.isRequired,
  me: shape({
    currentOrganization: shape({
      id: string.isRequired,
      name: string.isRequired,
    }),
    name: string,
    organizations: arrayOf(
      shape({
        id: string.isRequired,
        name: string.isRequired,
      })
    ),
    roles: arrayOf(
      shape({
        id: string,
        name: string,
      })
    ),
    role: string,
  }).isRequired,
  meChangeOrganization: func.isRequired,
  sourcePrefix: string.isRequired, // TODO: remove this prop once we move /users & /organizations above sources
}

const mapDispatchToProps = dispatch => ({
  meChangeOrganization: bindActionCreators(meChangeOrganizationAsync, dispatch),
})

export default connect(null, mapDispatchToProps)(UserNavBlock)
<|endoftext|>


<commit_before>import React, {PropTypes, Component} from 'react'
import {Link} from 'react-router'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'

import Authorized, {ADMIN_ROLE, SUPERADMIN_ROLE} from 'src/auth/Authorized'

import classnames from 'classnames'

import {meChangeOrganizationAsync} from 'shared/actions/auth'

class UserNavBlock extends Component {
  handleChangeCurrentOrganization = organizationID => () => {
    const {links, meChangeOrganization} = this.props
    meChangeOrganization(links.me, {organization: organizationID})
  }

  render() {
    const {
      logoutLink,
      links: {external: {custom: customLinks}},
      me,
      me: {currentOrganization, organizations, roles},
      sourcePrefix,
    } = this.props

    // TODO: find a better way to glean this information.
    // Need this method for when a user is a superadmin,
    // which doesn't reflect their role in the current org
    const currentRole = roles.find(cr => {
      return cr.organization === currentOrganization.id
    }).name

    return (

            {currentOrganization.name} ({currentRole})

            {me.name}

          <Authorized requiredRole={SUPERADMIN_ROLE}>
            <Link
              className="sidebar-menu--item"
              to={`${sourcePrefix}/organizations`}
            >
              Manage Organizations
            </Link>
          </Authorized>
          <Authorized requiredRole={ADMIN_ROLE}>
            <Link className="sidebar-menu--item" to={`${sourcePrefix}/users`}>
              Manage Users
            </Link>
          </Authorized>

            Logout

Switch Organizations

          {roles.map((r, i) => {
            const isLinkCurrentOrg = currentOrganization.id === r.organization
            return (

                {organizations.find(o => o.id === r.organization).name}{' '}
                ({r.name})

            )
          })}
          {customLinks
            ?
Custom Links

            : null}
          {customLinks
            ? customLinks.map((link, i) =>

                  {link.name}

              )
            : null}

    )
  }
}

const {arrayOf, func, shape, string} = PropTypes

UserNavBlock.propTypes = {
  links: shape({
    me: string,
    external: shape({
      custom: arrayOf(
        shape({
          name: string.isRequired,
          url: string.isRequired,
        })
      ),
    }),
  }),
  logoutLink: string.isRequired,
  me: shape({
    currentOrganization: shape({
      id: string.isRequired,
      name: string.isRequired,
    }),
    name: string,
    organizations: arrayOf(
      shape({
        id: string.isRequired,
        name: string.isRequired,
      })
    ),
    roles: arrayOf(
      shape({
        id: string,
        name: string,
      })
    ),
    role: string,
  }).isRequired,
  meChangeOrganization: func.isRequired,
  sourcePrefix: string.isRequired, // TODO: remove this prop once we move /users & /organizations above sources
}

const mapDispatchToProps = dispatch => ({
  meChangeOrganization: bindActionCreators(meChangeOrganizationAsync, dispatch),
})

export default connect(null, mapDispatchToProps)(UserNavBlock)
<commit_msg>Revert user nav block to exclude Manage Users & Manage Organizations<commit_after>import React, {PropTypes, Component} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'

import classnames from 'classnames'

import {meChangeOrganizationAsync} from 'shared/actions/auth'

class UserNavBlock extends Component {
  handleChangeCurrentOrganization = organizationID => () => {
    const {links, meChangeOrganization} = this.props
    meChangeOrganization(links.me, {organization: organizationID})
  }

  render() {
    const {
      logoutLink,
      links: {external: {custom: customLinks}},
      me,
      me: {currentOrganization, organizations, roles},
    } = this.props

    // TODO: find a better way to glean this information.
    // Need this method for when a user is a superadmin,
    // which doesn't reflect their role in the current org
    const currentRole = roles.find(cr => {
      return cr.organization === currentOrganization.id
    }).name

    return (

            {currentOrganization.name} ({currentRole})

            {me.name}

            Logout

Switch Organizations

          {roles.map((r, i) => {
            const isLinkCurrentOrg = currentOrganization.id === r.organization
            return (

                {organizations.find(o => o.id === r.organization).name}{' '}
                ({r.name})

            )
          })}
          {customLinks
            ?
Custom Links

            : null}
          {customLinks
            ? customLinks.map((link, i) =>

                  {link.name}

              )
            : null}

    )
  }
}

const {arrayOf, func, shape, string} = PropTypes

UserNavBlock.propTypes = {
  links: shape({
    me: string,
    external: shape({
      custom: arrayOf(
        shape({
          name: string.isRequired,
          url: string.isRequired,
        })
      ),
    }),
  }),
  logoutLink: string.isRequired,
  me: shape({
    currentOrganization: shape({
      id: string.isRequired,
      name: string.isRequired,
    }),
    name: string,
    organizations: arrayOf(
      shape({
        id: string.isRequired,
        name: string.isRequired,
      })
    ),
    roles: arrayOf(
      shape({
        id: string,
        name: string,
      })
    ),
    role: string,
  }).isRequired,
  meChangeOrganization: func.isRequired,
}

const mapDispatchToProps = dispatch => ({
  meChangeOrganization: bindActionCreators(meChangeOrganizationAsync, dispatch),
})

export default connect(null, mapDispatchToProps)(UserNavBlock)
<|endoftext|>


<commit_before>/*************************************************************
 *
 *  MathJax/extensions/TeX/mediawiki-texvc.js
 *
 *  Implements macros used by mediawiki with their texvc preprocessor.
 *
 *  ---------------------------------------------------------------------
 *
 *  Copyright (c) 2015-2017 The MathJax Consortium
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

MathJax.Extension["TeX/mediawiki-texvc"] = {
  version: "2.7.2"
};

MathJax.Hub.Register.StartupHook("TeX Jax Ready", function () {
  MathJax.InputJax.TeX.Definitions.Add({
    macros: {
      AA: ["Macro", "\u00c5"],
      alef: ["Macro", "\\aleph"],
      alefsym: ["Macro", "\\aleph"],
      Alpha: ["Macro", "\\mathrm{A}"],
      and: ["Macro", "\\land"],
      ang: ["Macro", "\\angle"],
      Bbb: ["Macro", "\\mathbb"],
      Beta: ["Macro", "\\mathrm{B}"],
      bold: ["Macro", "\\mathbf"],
      bull: ["Macro", "\\bullet"],
      C: ["Macro", "\\mathbb{C}"],
      Chi: ["Macro", "\\mathrm{X}"],
      clubs: ["Macro", "\\clubsuit"],
      cnums: ["Macro", "\\mathbb{C}"],
      Complex: ["Macro", "\\mathbb{C}"],
      coppa: ["Macro", "\u03D9"],
      Coppa: ["Macro", "\u03D8"],
      Dagger: ["Macro", "\\ddagger"],
      Digamma: ["Macro", "\u03DC"],
      darr: ["Macro", "\\downarrow"],
      dArr: ["Macro", "\\Downarrow"],
      Darr: ["Macro", "\\Downarrow"],
      dashint: ["Macro", "\\unicodeInt{x2A0D}"],
      ddashint: ["Macro", "\\unicodeInt{x2A0E}"],
      diamonds: ["Macro", "\\diamondsuit"],
      empty: ["Macro", "\\emptyset"],
      Epsilon: ["Macro", "\\mathrm{E}"],
      Eta: ["Macro", "\\mathrm{H}"],
      euro: ["Macro", "\u20AC"],
      exist: ["Macro", "\\exists"],
      geneuro: ["Macro", "\u20AC"],
      geneuronarrow: ["Macro", "\u20AC"],
      geneurowide: ["Macro", "\u20AC"],
      H: ["Macro", "\\mathbb{H}"],
      hAar: ["Macro", "\\Leftrightarrow"],
      harr: ["Macro", "\\leftrightarrow"],
      Harr: ["Macro", "\\Leftrightarrow"],
      hearts: ["Macro", "\\heartsuit"],
      image: ["Macro", "\\Im"],
      infin: ["Macro", "\\infty"],
      Iota: ["Macro", "\\mathrm{I}"],
      isin: ["Macro", "\\in"],
      Kappa: ["Macro", "\\mathrm{K}"],
      koppa: ["Macro", "\u03DF"],
      Koppa: ["Macro", "\u03DE"],
      lang: ["Macro", "\\langle"],
      larr: ["Macro", "\\leftarrow"],
      Larr: ["Macro", "\\Leftarrow"],
      lArr: ["Macro", "\\Leftarrow"],
      lrarr: ["Macro", "\\leftrightarrow"],
      Lrarr: ["Macro", "\\Leftrightarrow"],
      lrArr: ["Macro", "\\Leftrightarrow"],
      Mu: ["Macro", "\\mathrm{M}"],
      N: ["Macro", "\\mathbb{N}"],
      natnums: ["Macro", "\\mathbb{N}"],
      Nu: ["Macro", "\\mathrm{N}"],
      O: ["Macro", "\\emptyset"],
      oint: ["Macro", "\\unicodeInt{x222E}"],
      oiint: ["Macro", "\\unicodeInt{x222F}"],
      oiiint: ["Macro", "\\unicodeInt{x2230}"],
      ointctrclockwise: ["Macro", "\\unicodeInt{x2233}"],
      officialeuro: ["Macro", "\u20AC"],
      Omicron: ["Macro", "\\mathrm{O}"],
      or: ["Macro", "\\lor"],
      P: ["Macro", "\u00B6"],
      pagecolor: ['Macro','',1],  // ignore \pagecolor{}
      part: ["Macro", "\\partial"],
      plusmn: ["Macro", "\\pm"],
      Q: ["Macro", "\\mathbb{Q}"],
      R: ["Macro", "\\mathbb{R}"],
      rang: ["Macro", "\\rangle"],
      rarr: ["Macro", "\\rightarrow"],
      Rarr: ["Macro", "\\Rightarrow"],
      rArr: ["Macro", "\\Rightarrow"],
      real: ["Macro", "\\Re"],
      reals: ["Macro", "\\mathbb{R}"],
      Reals: ["Macro", "\\mathbb{R}"],
      Rho: ["Macro", "\\mathrm{P}"],
      sdot: ["Macro", "\\cdot"],
      sampi: ["Macro", "\u03E1"],
      Sampi: ["Macro", "\u03E0"],
      sect: ["Macro", "\\S"],
      spades: ["Macro", "\\spadesuit"],
      stigma: ["Macro", "\u03DB"],
      Stigma: ["Macro", "\u03DA"],
      sub: ["Macro", "\\subset"],
      sube: ["Macro", "\\subseteq"],
      supe: ["Macro", "\\supseteq"],
      Tau: ["Macro", "\\mathrm{T}"],
      textvisiblespace: ["Macro", "\u2423"],
      thetasym: ["Macro", "\\vartheta"],
      uarr: ["Macro", "\\uparrow"],
      uArr: ["Macro", "\\Uparrow"],
      Uarr: ["Macro", "\\Uparrow"],
      unicodeInt: ["Macro", "\\mathop{\\vcenter{\\mathchoice{\\huge\\unicode{#1}\\,}{\\unicode{#1}}{\\unicode{#1}}{\\unicode{#1}}}\\,}\\nolimits", 1],
      varcoppa: ["Macro", "\u03D9"],
      varstigma: ["Macro", "\u03DB"],
      varointclockwise: ["Macro", "\\unicodeInt{x2232}"],
      vline: ['Macro','\\smash{\\large\\lvert}',0],
      weierp: ["Macro", "\\wp"],
      Z: ["Macro", "\\mathbb{Z}"],
      Zeta: ["Macro", "\\mathrm{Z}"]
    }
  });
});

MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/mediawiki-texvc.js");
<commit_msg>Synced the TeXCV MediaWiki extension with the source at https://en.wikipedia.org/wiki/User:Nageh/mathJax/config/TeX-AMS-texvc_HTML.js<commit_after>/* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */

/*************************************************************
 *
 *  MathJax/extensions/TeX/mediawiki-texvc.js
 *
 *  Implements macros used by mediawiki with their texvc preprocessor.
 *
 *  ---------------------------------------------------------------------
 *
 *  Copyright (c) 2009-2017 The MathJax Consortium
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * From https://en.wikipedia.org/wiki/User:Nageh/mathJax/config/TeX-AMS-texvc_HTML.js
 */
/* global MathJax:true */

MathJax.Extension["TeX/mediawiki-texvc"] = {
  version: "2.7.2"
};

MathJax.Hub.Register.StartupHook("TeX Jax Ready", function () {

  var MML = MathJax.ElementJax.mml;

  // MathJax.Hub.Insert(MathJax.InputJax.TeX.Definitions,{
  MathJax.InputJax.TeX.Definitions.Add({
    mathchar0mi: {
      // Lowercase Greek letters
      coppa:        '03D9',  // archaic koppa
      koppa:        '03DF',
      stigma:       '03DB',
      thetasym:     '03B8',  // theta

      // Ord symbols
      AA:           ['00C5',{mathvariant: MML.VARIANT.NORMAL}],  // , used for Angstrom
      alef:         ['2135',{mathvariant: MML.VARIANT.NORMAL}],  // aleph
      alefsym:      ['2135',{mathvariant: MML.VARIANT.NORMAL}],  // aleph
      ang:          ['2220',{mathvariant: MML.VARIANT.NORMAL}],  // angle
      C:            ['0043',{mathvariant: MML.VARIANT.DOUBLESTRUCK}],
      clubs:        ['2663',{mathvariant: MML.VARIANT.NORMAL}],  // clubsuit
      cnums:        ['0043',{mathvariant: MML.VARIANT.DOUBLESTRUCK}],
      Complex:      ['0043',{mathvariant: MML.VARIANT.DOUBLESTRUCK}],
      diamonds:     ['2662',{mathvariant: MML.VARIANT.NORMAL}],  // diamondsuit
      empty:        ['2205',{mathvariant: MML.VARIANT.NORMAL}],  // emptyset
      exist:        ['2203',{mathvariant: MML.VARIANT.NORMAL}],  // exists
      H:            ['210D',{mathvariant: MML.VARIANT.DOUBLESTRUCK}],
      hearts:       ['2661',{mathvariant: MML.VARIANT.NORMAL}],  // heartsuit
      infin:        ['221E',{mathvariant: MML.VARIANT.NORMAL}],  // infty
      N:            ['004E',{mathvariant: MML.VARIANT.DOUBLESTRUCK}],
      natnums:      ['004E',{mathvariant: MML.VARIANT.DOUBLESTRUCK}],
      O:            ['2205',{mathvariant: MML.VARIANT.NORMAL}],  // emptyset (but should probably be Swedish O)
      P:            '00B6',
      part:         ['2202',{mathvariant: MML.VARIANT.NORMAL}],  // partial
      Q:            ['0051',{mathvariant: MML.VARIANT.DOUBLESTRUCK}],
      R:            ['0052',{mathvariant: MML.VARIANT.DOUBLESTRUCK}],
      real:         ['211C',{mathvariant: MML.VARIANT.NORMAL}],  // Re
      reals:        ['0052',{mathvariant: MML.VARIANT.DOUBLESTRUCK}],
      Reals:        ['0052',{mathvariant: MML.VARIANT.DOUBLESTRUCK}],
      sect:         '00A7',  // S
      spades:       ['2660',{mathvariant: MML.VARIANT.NORMAL}],  // spadesuit
      textvisiblespace: '2423',
      weierp:       ['2118',{mathvariant: MML.VARIANT.NORMAL}],  // wp
      Z:            ['005A',{mathvariant: MML.VARIANT.DOUBLESTRUCK}],
    },

    mathchar0mo: {
      // Binary operators
      and:          '2227',  // land
      bull:         '2219',  // bullet
      or:           '2228',  // lor
      plusmn:       '00B1',  // pm
      sdot:         '22C5',  // cdot

      // Binary relations
      isin:         '2208',  // in
      sub:          '2282',  // subset
      sube:         '2286',  // subseteq
      sup:          '2283',  // supset
      supe:         '2287',  // supseteq

      gets:               '2190',  // leftarrow
      hAar:               '21D4',  // Leftrightarrow [sic]
      harr:               '2194',  // leftrightarrow
      hArr:               '21D4',  // Leftrightarrow
      Harr:               '21D4',  // Leftrightarrow
      larr:               '2190',  // leftarrow
      lArr:               '21D0',  // Leftarrow
      Larr:               '21D0',  // Leftarrow
      lrarr:              '2194',  // leftrightarrow
      Lrarr:              '21D4',  // Leftrightarrow
      lrArr:              '21D4',  // Leftrightarrow
      rarr:               '2192',  // rightarrow
      rArr:               '21D2',  // Rightarrow
      Rarr:               '21D2',  // Rightarrow

      // big ops
      oiint:              ['222F',{texClass: MML.TEXCLASS.OP}],  // not part of texvc but nice to have
      oiiint:             ['2230',{texClass: MML.TEXCLASS.OP}],
    },

    mathchar7: {
      // Uppercase Greek letters
      Alpha:        '0391',
      Beta:         '0392',
      Chi:          '03A7',
      Epsilon:      '0395',
      Eta:          '0397',
      Iota:         '0399',
      Kappa:        '039A',
      Mu:           '039C',
      Nu:           '039D',
      Omicron:      '039F',
      Rho:          '03A1',
      Tau:          '03A4',
      Zeta:         '0396',

      Coppa:        '03D8',  // archaic Koppa
      Koppa:        '03DE',
      Stigma:       '03DA',
    },

    delimiter: {
      '\\darr':           '2193',  // downarrow
      '\\Darr':           '21D3',  // Downarrow
      '\\dArr':           '21D3',  // Downarrow
      '\\lang':           '27E8',  // langle
      '\\rang':           '27E9',  // rangle
      '\\uarr':           '2191',  // uparrow
      '\\Uarr':           '21D1',  // Uparrow
      '\\uArr':           '21D1',  // Uparrow
    },

    macros: {
      AA: ["Macro", "\u00c5"],
      alef: ["Macro", "\\aleph"],
      alefsym: ["Macro", "\\aleph"],
      Alpha: ["Macro", "\\mathrm{A}"],
      and: ["Macro", "\\land"],
      ang: ["Macro", "\\angle"],

      arccot:             'NamedFn',
      arccsc:             'NamedFn',
      arcsec:             'NamedFn',

      Bbb: ["Macro", "\\mathbb"],
      Beta: ["Macro", "\\mathrm{B}"],

      bold:               ['Macro','\\mathbf{#1}',1],  // boldsymbol

      bull: ["Macro", "\\bullet"],
      C: ["Macro", "\\mathbb{C}"],
      Chi: ["Macro", "\\mathrm{X}"],
      clubs: ["Macro", "\\clubsuit"],
      cnums: ["Macro", "\\mathbb{C}"],
      Complex: ["Macro", "\\mathbb{C}"],
      coppa: ["Macro", "\u03D9"],
      Coppa: ["Macro", "\u03D8"],
      Dagger: ["Macro", "\\ddagger"],
      Digamma: ["Macro", "\u03DC"],
      darr: ["Macro", "\\downarrow"],
      dArr: ["Macro", "\\Downarrow"],
      Darr: ["Macro", "\\Downarrow"],
      dashint: ["Macro", "\\unicodeInt{x2A0D}"],
      ddashint: ["Macro", "\\unicodeInt{x2A0E}"],
      diamonds: ["Macro", "\\diamondsuit"],
      empty: ["Macro", "\\emptyset"],
      Epsilon: ["Macro", "\\mathrm{E}"],
      Eta: ["Macro", "\\mathrm{H}"],
      euro: ["Macro", "\u20AC"],
      exist: ["Macro", "\\exists"],
      geneuro: ["Macro", "\u20AC"],
      geneuronarrow: ["Macro", "\u20AC"],
      geneurowide: ["Macro", "\u20AC"],
      H: ["Macro", "\\mathbb{H}"],
      hAar: ["Macro", "\\Leftrightarrow"],
      harr: ["Macro", "\\leftrightarrow"],
      Harr: ["Macro", "\\Leftrightarrow"],
      hearts: ["Macro", "\\heartsuit"],

      href:               'NamedFn', // disable dangerous command

      image:              ['Macro','\\Im'],

      infin: ["Macro", "\\infty"],
      Iota: ["Macro", "\\mathrm{I}"],
      isin: ["Macro", "\\in"],
      Kappa: ["Macro", "\\mathrm{K}"],
      koppa: ["Macro", "\u03DF"],
      Koppa: ["Macro", "\u03DE"],
      lang: ["Macro", "\\langle"],
      larr: ["Macro", "\\leftarrow"],
      Larr: ["Macro", "\\Leftarrow"],
      lArr: ["Macro", "\\Leftarrow"],
      lrarr: ["Macro", "\\leftrightarrow"],
      Lrarr: ["Macro", "\\Leftrightarrow"],
      lrArr: ["Macro", "\\Leftrightarrow"],
      Mu: ["Macro", "\\mathrm{M}"],
      N: ["Macro", "\\mathbb{N}"],
      natnums: ["Macro", "\\mathbb{N}"],
      Nu: ["Macro", "\\mathrm{N}"],
      O: ["Macro", "\\emptyset"],
      oint: ["Macro", "\\unicodeInt{x222E}"],
      oiint: ["Macro", "\\unicodeInt{x222F}"],
      oiiint: ["Macro", "\\unicodeInt{x2230}"],
      ointctrclockwise: ["Macro", "\\unicodeInt{x2233}"],
      officialeuro: ["Macro", "\u20AC"],
      Omicron: ["Macro", "\\mathrm{O}"],
      or: ["Macro", "\\lor"],
      P: ["Macro", "\u00B6"],

      pagecolor:          ['Macro','',1],  // ignore \pagecolor{}

      part: ["Macro", "\\partial"],
      plusmn: ["Macro", "\\pm"],
      Q: ["Macro", "\\mathbb{Q}"],
      R: ["Macro", "\\mathbb{R}"],
      rang: ["Macro", "\\rangle"],
      rarr: ["Macro", "\\rightarrow"],
      Rarr: ["Macro", "\\Rightarrow"],
      rArr: ["Macro", "\\Rightarrow"],
      real: ["Macro", "\\Re"],
      reals: ["Macro", "\\mathbb{R}"],
      Reals: ["Macro", "\\mathbb{R}"],
      Rho: ["Macro", "\\mathrm{P}"],
      sdot: ["Macro", "\\cdot"],
      sampi: ["Macro", "\u03E1"],
      Sampi: ["Macro", "\u03E0"],
      sect: ["Macro", "\\S"],

      sgn:                'NamedFn',

      spades: ["Macro", "\\spadesuit"],
      stigma: ["Macro", "\u03DB"],
      Stigma: ["Macro", "\u03DA"],

      style:              'NamedFn', // disable dangerous command

      sub: ["Macro", "\\subset"],
      sube: ["Macro", "\\subseteq"],
      supe: ["Macro", "\\supseteq"],
      Tau: ["Macro", "\\mathrm{T}"],
      textvisiblespace: ["Macro", "\u2423"],
      thetasym: ["Macro", "\\vartheta"],
      uarr: ["Macro", "\\uparrow"],
      uArr: ["Macro", "\\Uparrow"],
      Uarr: ["Macro", "\\Uparrow"],
      unicodeInt: ["Macro", "\\mathop{\\vcenter{\\mathchoice{\\huge\\unicode{#1}\\,}{\\unicode{#1}}{\\unicode{#1}}{\\unicode{#1}}}\\,}\\nolimits", 1],
      varcoppa: ["Macro", "\u03D9"],
      varstigma: ["Macro", "\u03DB"],
      varointclockwise: ["Macro", "\\unicodeInt{x2232}"],

      vline:              ['Macro','\\smash{\\large\\lvert}',0],

      weierp: ["Macro", "\\wp"],
      Z: ["Macro", "\\mathbb{Z}"],
      Zeta: ["Macro", "\\mathrm{Z}"],
    }
  });
});

MathJax.Hub.Startup.signal.Post('TeX MediaWiki TeXVC Ready');

MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/mediawiki-texvc.js");
<|endoftext|>