<commit_before>var plotdata = [];
var event_vis_feed_data;
var ajaxAsyncXdr = [];
var compare_unit = 0;
var hidden_lines = {};
var xaxis_format = "";

function create_plotlist(feedid, fill, depth){
  var plotlist = [];
  var unit = "";
  var unit_plural = "s";

  switch(compare_unit) {
    case 1000*60*60*24:
      unit = "Day";
      xaxis_format = "%H:%M";
      break;
    case 1000*60*60*24*7:
      unit = "Week";
      xaxis_format = "%a
%H:%M";
      break;
    case 1000*60*60*24*28:
      unit = "Month";
      xaxis_format = "%a
%H:%M";
      break;
    case 1000*60*60*24*365:
      unit = "Year";
      xaxis_format = "%m/%d
%a
%H:%M";
      break;
    default:
      unit = compare_unit / 1000 + " Seconds";
      unit_plural = "";
      xaxis_format = "%H:%M";
  }

  for (var i = 0; i < depth; i++) {
    var cur_depth = depth - i - 1;
    var label;

    if (0 == cur_depth) {
      label = "Current";
    } else if (1 == cur_depth) {
      label = cur_depth + " " + unit + " Prior";
    } else {
      label = cur_depth + " " + unit + unit_plural + " Prior";
    }

    plotlist[i] = {
      id: feedid,
      selected: 1,
      depth: cur_depth,
      plot:
      {
        idx: i,
        data: null,
        label: label,
        yaxis: 1,
        lines:
        {
          show: true,
          fill: fill
        }
      }
    };
  }

  return plotlist;
}
  /*
  Handle_feeds
  */
  //ignore multiple fast load feed requests
function vis_feed_data(){
   clearTimeout(event_vis_feed_data); // cancel pending event
   event_vis_feed_data = setTimeout(function(){ vis_feed_data_delayed(); }, 500);
   plot();
}

// Load relevant feed data asynchronously
function vis_feed_data_delayed(){
  compare_unit = view.end - view.start;
  fill = fill > 0 ? true : false;
  if (depth <= 0) depth = 3;

 var plotlist = create_plotlist(feedid, fill, depth);
  for(var i in plotlist) {
    if (plotlist[i].selected) {
      if (!plotlist[i].plot.data)
      {
        var npoints = 800;
        var plot_start = view.start - (compare_unit * plotlist[i].depth); // Need to take into account leapyear
        var plot_end = view.end - (compare_unit * plotlist[i].depth);
        interval = Math.round((view.end - view.start)/(npoints * 1000));
        var skipmissing = 0;

        if (plotdata[i] === undefined) plotdata[i] = [];

        if (typeof ajaxAsyncXdr[i] !== 'undefined') {
          ajaxAsyncXdr[i].abort(); // Abort pending loads
          ajaxAsyncXdr[i] = undefined;
        }
        var context = {index:i, plotlist:plotlist[i]};
        ajaxAsyncXdr[i] = get_feed_data_async(vis_feed_data_callback, context, plotlist[i].id, plot_start, plot_end, interval,skipmissing, 1);
      }
    }
  }
}

// Asynchronous callback for loading data
function vis_feed_data_callback(context, data){
  var i = context['index'];
  var depth = context['plotlist'].depth;

  for (var d in data) {
    data[d][0] = data[d][0] + (compare_unit * depth); // Adjust the old data to be visible on the current graph
  }

  if(i in hidden_lines) {
     context['plotlist'].plot.visible = false;
  } else {
     context['plotlist'].plot.visible = true;
  }

  context['plotlist'].plot.data = data;
  if (context['plotlist'].plot.data) {
    plotdata[i] = context['plotlist'].plot;
  }
  plot();
}

  function toggle_line(idx){
    //plotdata[idx].lines.show = !plotdata[idx].lines.show;
    plotdata[idx].visible = !plotdata[idx].visible;
    if(!plotdata[idx].visible){
      hidden_lines[idx] = true;
    } else {
      delete hidden_lines[idx];
    }

    plot();
  }

/*
 Graphing Functions
*/

function plot(){
  $.plot($("#graph"), plotdata, {
    grid: { show: true, hoverable: true, clickable: true },
    xaxis: { mode: "time", timezone: "browser", timeformat: xaxis_format, min: view.start, max: view.end },
    selection: { mode: "x" },
    legend: { position: "nw",
      labelFormatter: function(label, plot){
        var colour = plot.idx in hidden_lines ? "gray" : "black";
        return ''+label+'';
      }
    },
    touch: { pan: "x", scale: "x"}
  });
}

function timecompare_init(element){
  // Get start and end time of view based on default scale and current time
  plotdata = [];
  compare_unit = (1000*60*60*24.0*7); // One week in milliseconds
  var now = new Date().getTime();
  view.start = now - compare_unit;
  view.end = now;

  var out =
    "
"+
      "

"+
      "
"+
        "
"+
        "Tooltip:"+
        "<input id='enableTooltip' type='checkbox' checked >"+
        "
 "+

        "
"+
        "<button class='btn graph-time' type='button' time='1'>D</button>"+
        "<button class='btn graph-time' type='button' time='7'>W</button>"+
        "<button class='btn graph-time' type='button' time='28'>M</button>"+
        "<button class='btn graph-time' type='button' time='365'>Y</button>
"+

        "
"+
        "<button class='btn graph-nav' id='zoomin'>+</button>"+
        "<button class='btn graph-nav' id='zoomout'>-</button>"+
        "<button class='btn graph-nav' id='left'><</button>"+
        "<button class='btn graph-nav' id='right'>></button>
"+
      "
"+
    "
"
  ;
  $(element).html(out);

  $('#graph').width($('#graph_bound').width());
  $('#graph').height($('#graph_bound').height());
  if (embed) $('#graph').height($(window).height());

  $(window).resize(function(){
    $('#graph').width($('#graph_bound').width());
    if (embed) $('#graph').height($(window).height());
    plot();
  });

  //-----------------
  // Graph zooming
  //-----------------
  $("#graph").bind("plotselected", function (event, ranges){
     view.start = ranges.xaxis.from;
     view.end = ranges.xaxis.to;
     vis_feed_data();
  });

  //-----------------
  // Operate buttons
  //-----------------
  $("#zoomout").click(function () {view.zoomout(); vis_feed_data();});
  $("#zoomin").click(function () {view.zoomin(); vis_feed_data();});
  $('#right').click(function () {view.panright(); vis_feed_data();});
  $('#left').click(function () {view.panleft(); vis_feed_data();});
  $('.graph-time').click(function () {view.timewindow($(this).attr("time")); vis_feed_data();});
  //-----------------

  // Graph buttons and navigation efects for mouse and touch
  $("#graph").mouseenter(function(){
      $("#graph-navbar").show();
      $("#graph-tooltip").show();
      $("#graph-buttons").stop().fadeIn();
      $("#stats").stop().fadeIn();
  });
  $("#graph_bound").mouseleave(function(){
      $("#graph-buttons").stop().fadeOut();
      $("#stats").stop().fadeOut();
  });
  $("#graph").bind("touchstarted", function (event, pos){
      $("#graph-navbar").hide();
      $("#graph-tooltip").hide();
      $("#graph-buttons").stop().fadeOut();
      $("#stats").stop().fadeOut();
  });
  $("#graph").bind("touchended", function (event, ranges){
      $("#graph-buttons").stop().fadeIn();
      $("#stats").stop().fadeIn();
      view.start = ranges.xaxis.from;
      view.end = ranges.xaxis.to;
      vis_feed_data();
  });
}
<commit_msg>Timecompare tooltip fix.<commit_after>var plotdata = [];
var event_vis_feed_data;
var ajaxAsyncXdr = [];
var compare_unit = 0;
var hidden_lines = {};
var xaxis_format = "";

function create_plotlist(feedid, fill, depth){
  var plotlist = [];
  var unit = "";
  var unit_plural = "s";

  switch(compare_unit) {
    case 1000*60*60*24:
      unit = "Day";
      xaxis_format = "%H:%M";
      break;
    case 1000*60*60*24*7:
      unit = "Week";
      xaxis_format = "%a
%H:%M";
      break;
    case 1000*60*60*24*28:
      unit = "Month";
      xaxis_format = "%a
%H:%M";
      break;
    case 1000*60*60*24*365:
      unit = "Year";
      xaxis_format = "%m/%d
%a
%H:%M";
      break;
    default:
      unit = compare_unit / 1000 + " Seconds";
      unit_plural = "";
      xaxis_format = "%H:%M";
  }

  for (var i = 0; i < depth; i++) {
    var cur_depth = depth - i - 1;
    var label;

    if (0 == cur_depth) {
      label = "Current";
    } else if (1 == cur_depth) {
      label = cur_depth + " " + unit + " Prior";
    } else {
      label = cur_depth + " " + unit + unit_plural + " Prior";
    }

    plotlist[i] = {
      id: feedid,
      selected: 1,
      depth: cur_depth,
      plot:
      {
        idx: i,
        adj: compare_unit * cur_depth,
        data: null,
        label: label,
        yaxis: 1,
        lines:
        {
          show: true,
          fill: fill
        }
      }
    };
  }

  return plotlist;
}
  /*
  Handle_feeds
  */
  //ignore multiple fast load feed requests
function vis_feed_data(){
   clearTimeout(event_vis_feed_data); // cancel pending event
   event_vis_feed_data = setTimeout(function(){ vis_feed_data_delayed(); }, 500);
   plot();
}

// Load relevant feed data asynchronously
function vis_feed_data_delayed(){
  compare_unit = view.end - view.start;
  fill = fill > 0 ? true : false;
  if (depth <= 0) depth = 3;

 var plotlist = create_plotlist(feedid, fill, depth);
  for(var i in plotlist) {
    if (plotlist[i].selected) {
      if (!plotlist[i].plot.data)
      {
        var npoints = 800;
        var plot_start = view.start - (compare_unit * plotlist[i].depth); // Need to take into account leapyear
        var plot_end = view.end - (compare_unit * plotlist[i].depth);
        interval = Math.round((view.end - view.start)/(npoints * 1000));
        var skipmissing = 0;

        if (plotdata[i] === undefined) plotdata[i] = [];

        if (typeof ajaxAsyncXdr[i] !== 'undefined') {
          ajaxAsyncXdr[i].abort(); // Abort pending loads
          ajaxAsyncXdr[i] = undefined;
        }
        var context = {index:i, plotlist:plotlist[i]};
        ajaxAsyncXdr[i] = get_feed_data_async(vis_feed_data_callback, context, plotlist[i].id, plot_start, plot_end, interval,skipmissing, 1);
      }
    }
  }
}

// Asynchronous callback for loading data
function vis_feed_data_callback(context, data){
  var i = context['index'];
  var depth = context['plotlist'].depth;

  for (var d in data) {
    data[d][0] = data[d][0] + (compare_unit * depth); // Adjust the old data to be visible on the current graph
  }

  if(i in hidden_lines) {
     context['plotlist'].plot.visible = false;
  } else {
     context['plotlist'].plot.visible = true;
  }

  context['plotlist'].plot.data = data;
  if (context['plotlist'].plot.data) {
    plotdata[i] = context['plotlist'].plot;
  }
  plot();
}

  function toggle_line(idx){
    //plotdata[idx].lines.show = !plotdata[idx].lines.show;
    plotdata[idx].visible = !plotdata[idx].visible;
    if(!plotdata[idx].visible){
      hidden_lines[idx] = true;
    } else {
      delete hidden_lines[idx];
    }

    plot();
  }

/*
 Graphing Functions
*/

function plot(){
  $.plot($("#graph"), plotdata, {
    grid: { show: true, hoverable: true, clickable: true },
    xaxis: { mode: "time", timezone: "browser", timeformat: xaxis_format, min: view.start, max: view.end },
    selection: { mode: "x" },
    legend: { position: "nw",
      labelFormatter: function(label, plot){
        var colour = plot.idx in hidden_lines ? "gray" : "black";
        return ''+label+'';
      }
    },
    touch: { pan: "x", scale: "x"}
  });
}

function timecompare_init(element){
  // Get start and end time of view based on default scale and current time
  plotdata = [];
  compare_unit = (1000*60*60*24.0*7); // One week in milliseconds
  var now = new Date().getTime();
  view.start = now - compare_unit;
  view.end = now;

  var out =
    "
"+
      "

"+
      "
"+
        "
"+
        "Tooltip:"+
        "<input id='enableTooltip' type='checkbox' checked >"+
        "
 "+

        "
"+
        "<button class='btn graph-time' type='button' time='1'>D</button>"+
        "<button class='btn graph-time' type='button' time='7'>W</button>"+
        "<button class='btn graph-time' type='button' time='28'>M</button>"+
        "<button class='btn graph-time' type='button' time='365'>Y</button>
"+

        "
"+
        "<button class='btn graph-nav' id='zoomin'>+</button>"+
        "<button class='btn graph-nav' id='zoomout'>-</button>"+
        "<button class='btn graph-nav' id='left'><</button>"+
        "<button class='btn graph-nav' id='right'>></button>
"+
      "
"+
    "
"
  ;
  $(element).html(out);

  $('#graph').width($('#graph_bound').width());
  $('#graph').height($('#graph_bound').height());
  if (embed) $('#graph').height($(window).height());

  $(window).resize(function(){
    $('#graph').width($('#graph_bound').width());
    if (embed) $('#graph').height($(window).height());
    plot();
  });

  //-----------------
  // Graph zooming
  //-----------------
  $("#graph").bind("plotselected", function (event, ranges){
     view.start = ranges.xaxis.from;
     view.end = ranges.xaxis.to;
     vis_feed_data();
  });

  //-----------------
  // Operate buttons
  //-----------------
  $("#zoomout").click(function () {view.zoomout(); vis_feed_data();});
  $("#zoomin").click(function () {view.zoomin(); vis_feed_data();});
  $('#right').click(function () {view.panright(); vis_feed_data();});
  $('#left').click(function () {view.panleft(); vis_feed_data();});
  $('.graph-time').click(function () {view.timewindow($(this).attr("time")); vis_feed_data();});
  //-----------------

  // Graph buttons and navigation efects for mouse and touch
  $("#graph").mouseenter(function(){
      $("#graph-navbar").show();
      $("#graph-tooltip").show();
      $("#graph-buttons").stop().fadeIn();
      $("#stats").stop().fadeIn();
  });
  $("#graph_bound").mouseleave(function(){
      $("#graph-buttons").stop().fadeOut();
      $("#stats").stop().fadeOut();
  });
  $("#graph").bind("touchstarted", function (event, pos){
      $("#graph-navbar").hide();
      $("#graph-tooltip").hide();
      $("#graph-buttons").stop().fadeOut();
      $("#stats").stop().fadeOut();
  });
  $("#graph").bind("touchended", function (event, ranges){
      $("#graph-buttons").stop().fadeIn();
      $("#stats").stop().fadeIn();
      view.start = ranges.xaxis.from;
      view.end = ranges.xaxis.to;
      vis_feed_data();
  });
}
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');
const { expect } = require('chai');
const verquire = require('../../utils/verquire');
const testUtils = require('../../utils/index');

const Excel = verquire('excel');

const TEST_XLSX_FILE_NAME = './spec/out/wb.test.xlsx';

describe('WorkbookWriter', () => {
  it('creates sheets with correct names', () => {
    const wb = new Excel.stream.xlsx.WorkbookWriter();
    const ws1 = wb.addWorksheet('Hello, World!');
    expect(ws1.name).to.equal('Hello, World!');

    const ws2 = wb.addWorksheet();
    expect(ws2.name).to.match(/sheet\d+/);
  });

  describe('Serialise', () => {
    it('xlsx file', () => {
      const options = {
        filename: TEST_XLSX_FILE_NAME,
        useStyles: true,
      };
      const wb = testUtils.createTestBook(
        new Excel.stream.xlsx.WorkbookWriter(options),
        'xlsx'
      );

      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          testUtils.checkTestBook(wb2, 'xlsx');
        });
    });

    it('shared formula', () => {
      const options = {
        filename: TEST_XLSX_FILE_NAME,
        useStyles: false,
      };
      const wb = new Excel.stream.xlsx.WorkbookWriter(options);
      const ws = wb.addWorksheet('Hello');
      ws.getCell('A1').value = { formula: 'ROW()+COLUMN()', result: 2 };
      ws.getCell('B1').value = { sharedFormula: 'A1', result: 3 };
      ws.getCell('A2').value = { sharedFormula: 'A1', result: 3 };
      ws.getCell('B2').value = { sharedFormula: 'A1', result: 4 };

      ws.commit();
      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          const ws2 = wb2.getWorksheet('Hello');
          expect(ws2.getCell('A1').value).to.deep.equal({
            formula: 'ROW()+COLUMN()',
            result: 2,
          });
          expect(ws2.getCell('B1').value).to.deep.equal({
            sharedFormula: 'A1',
            result: 3,
          });
          expect(ws2.getCell('A2').value).to.deep.equal({
            sharedFormula: 'A1',
            result: 3,
          });
          expect(ws2.getCell('B2').value).to.deep.equal({
            sharedFormula: 'A1',
            result: 4,
          });
        });
    });

    it('auto filter', () => {
      const options = {
        filename: TEST_XLSX_FILE_NAME,
        useStyles: false,
      };
      const wb = new Excel.stream.xlsx.WorkbookWriter(options);
      const ws = wb.addWorksheet('Hello');
      ws.getCell('A1').value = 1;
      ws.getCell('B1').value = 1;
      ws.getCell('A2').value = 2;
      ws.getCell('B2').value = 2;
      ws.getCell('A3').value = 3;
      ws.getCell('B3').value = 3;

      ws.autoFilter = 'A1:B1';
      ws.commit();

      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          const ws2 = wb2.getWorksheet('Hello');
          expect(ws2.autoFilter).to.equal('A1:B1');
        });
    });

    it('Without styles', () => {
      const options = {
        filename: TEST_XLSX_FILE_NAME,
        useStyles: false,
      };
      const wb = testUtils.createTestBook(
        new Excel.stream.xlsx.WorkbookWriter(options),
        'xlsx'
      );

      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          testUtils.checkTestBook(wb2, 'xlsx', undefined, {
            checkStyles: false,
          });
        });
    });

    it('serializes row styles and columns properly', () => {
      const options = {
        filename: TEST_XLSX_FILE_NAME,
        useStyles: true,
      };
      const wb = new Excel.stream.xlsx.WorkbookWriter(options);
      const ws = wb.addWorksheet('blort');

      const colStyle = {
        font: testUtils.styles.fonts.comicSansUdB16,
        alignment: testUtils.styles.namedAlignments.middleCentre,
      };
      ws.columns = [
        { header: 'A1', width: 10 },
        { header: 'B1', style: colStyle },
        { header: 'C1', width: 30 },
        { header: 'D1' },
      ];

      ws.getRow(2).font = testUtils.styles.fonts.broadwayRedOutline20;

      ws.getCell('A2').value = 'A2';
      ws.getCell('B2').value = 'B2';
      ws.getCell('C2').value = 'C2';
      ws.getCell('A3').value = 'A3';
      ws.getCell('B3').value = 'B3';
      ws.getCell('C3').value = 'C3';

      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          const ws2 = wb2.getWorksheet('blort');
          ['A1', 'B1', 'C1', 'A2', 'B2', 'C2', 'A3', 'B3', 'C3'].forEach(
            address => {
              expect(ws2.getCell(address).value).to.equal(address);
            }
          );
          expect(ws2.getCell('B1').font).to.deep.equal(
            testUtils.styles.fonts.comicSansUdB16
          );
          expect(ws2.getCell('B1').alignment).to.deep.equal(
            testUtils.styles.namedAlignments.middleCentre
          );
          expect(ws2.getCell('A2').font).to.deep.equal(
            testUtils.styles.fonts.broadwayRedOutline20
          );
          expect(ws2.getCell('B2').font).to.deep.equal(
            testUtils.styles.fonts.broadwayRedOutline20
          );
          expect(ws2.getCell('C2').font).to.deep.equal(
            testUtils.styles.fonts.broadwayRedOutline20
          );
          expect(ws2.getCell('B3').font).to.deep.equal(
            testUtils.styles.fonts.comicSansUdB16
          );
          expect(ws2.getCell('B3').alignment).to.deep.equal(
            testUtils.styles.namedAlignments.middleCentre
          );

          expect(ws2.getColumn(2).font).to.deep.equal(
            testUtils.styles.fonts.comicSansUdB16
          );
          expect(ws2.getColumn(2).alignment).to.deep.equal(
            testUtils.styles.namedAlignments.middleCentre
          );
          expect(ws2.getColumn(2).width).to.equal(undefined);

          expect(ws2.getColumn(4).width).to.equal(undefined);

          expect(ws2.getRow(2).font).to.deep.equal(
            testUtils.styles.fonts.broadwayRedOutline20
          );
        });
    });

    it('rich text', () => {
      const options = {
        filename: TEST_XLSX_FILE_NAME,
        useStyles: true,
      };
      const wb = new Excel.stream.xlsx.WorkbookWriter(options);
      const ws = wb.addWorksheet('Hello');

      ws.getCell('A1').value = {
        richText: [
          {
            font: { color: { argb: 'FF0000' } },
            text: 'red ',
          },
          {
            font: { color: { argb: '00FF00' }, bold: true },
            text: ' bold green',
          },
        ],
      };

      ws.getCell('B1').value = 'plain text';

      ws.commit();
      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          const ws2 = wb2.getWorksheet('Hello');
          expect(ws2.getCell('A1').value).to.deep.equal({
            richText: [
              {
                font: { color: { argb: 'FF0000' } },
                text: 'red ',
              },
              {
                font: { color: { argb: '00FF00' }, bold: true },
                text: ' bold green',
              },
            ],
          });
          expect(ws2.getCell('B1').value).to.equal('plain text');
        });
    });

    it('A lot of sheets', function() {
      this.timeout(5000);

      let i;
      const wb = new Excel.stream.xlsx.WorkbookWriter({
        filename: TEST_XLSX_FILE_NAME,
      });
      const numSheets = 90;
      // add numSheets sheets
      for (i = 1; i <= numSheets; i++) {
        const ws = wb.addWorksheet(`sheet${i}`);
        ws.getCell('A1').value = i;
      }
      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          for (i = 1; i <= numSheets; i++) {
            const ws2 = wb2.getWorksheet(`sheet${i}`);
            expect(ws2).to.be.ok();
            expect(ws2.getCell('A1').value).to.equal(i);
          }
        });
    });

    it('addRow', () => {
      const options = {
        stream: fs.createWriteStream(TEST_XLSX_FILE_NAME, { flags: 'w' }),
        useStyles: true,
        useSharedStrings: true,
      };
      const workbook = new Excel.stream.xlsx.WorkbookWriter(options);
      const worksheet = workbook.addWorksheet('test');
      const newRow = worksheet.addRow(['hello']);
      newRow.commit();
      worksheet.commit();
      return workbook.commit();
    });

    it('defined names', () => {
      const wb = new Excel.stream.xlsx.WorkbookWriter({
        filename: TEST_XLSX_FILE_NAME,
      });
      const ws = wb.addWorksheet('blort');
      ws.getCell('A1').value = 5;
      ws.getCell('A1').name = 'five';

      ws.getCell('A3').value = 'drei';
      ws.getCell('A3').name = 'threes';
      ws.getCell('B3').value = 'trois';
      ws.getCell('B3').name = 'threes';
      ws.getCell('B3').value = 'san';
      ws.getCell('B3').name = 'threes';

      ws.getCell('E1').value = 'grün';
      ws.getCell('E1').name = 'greens';
      ws.getCell('E2').value = 'vert';
      ws.getCell('E2').name = 'greens';
      ws.getCell('E3').value = 'verde';
      ws.getCell('E3').name = 'greens';

      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          const ws2 = wb2.getWorksheet('blort');
          expect(ws2.getCell('A1').name).to.equal('five');

          expect(ws2.getCell('A3').name).to.equal('threes');
          expect(ws2.getCell('B3').name).to.equal('threes');
          expect(ws2.getCell('B3').name).to.equal('threes');

          expect(ws2.getCell('E1').name).to.equal('greens');
          expect(ws2.getCell('E2').name).to.equal('greens');
          expect(ws2.getCell('E3').name).to.equal('greens');
        });
    });

    it('does not escape special xml characters', () => {
      const wb = new Excel.stream.xlsx.WorkbookWriter({
        filename: TEST_XLSX_FILE_NAME,
        useSharedStrings: true,
      });
      const ws = wb.addWorksheet('blort');
      const xmlCharacters = 'xml characters: & < > "';

      ws.getCell('A1').value = xmlCharacters;

      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          const ws2 = wb2.getWorksheet('blort');
          expect(ws2.getCell('A1').value).to.equal(xmlCharacters);
        });
    });

    it('serializes and deserializes dataValidations', () => {
      const options = { filename: TEST_XLSX_FILE_NAME };
      const wb = testUtils.createTestBook(
        new Excel.stream.xlsx.WorkbookWriter(options),
        'xlsx',
        ['dataValidations']
      );

      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          testUtils.checkTestBook(wb2, 'xlsx', ['dataValidations']);
        });
    });
  });
});
<commit_msg>Add Compression level option to WorkbookWriterOptions for streaming #888<commit_after>'use strict';

const fs = require('fs');
const { expect } = require('chai');
const verquire = require('../../utils/verquire');
const testUtils = require('../../utils/index');

const Excel = verquire('excel');

const TEST_XLSX_FILE_NAME = './spec/out/wb.test.xlsx';

describe('WorkbookWriter', () => {
  it('creates sheets with correct names', () => {
    const wb = new Excel.stream.xlsx.WorkbookWriter();
    const ws1 = wb.addWorksheet('Hello, World!');
    expect(ws1.name).to.equal('Hello, World!');

    const ws2 = wb.addWorksheet();
    expect(ws2.name).to.match(/sheet\d+/);
  });

  describe('Serialise', () => {
    it('xlsx file', () => {
      const options = {
        filename: TEST_XLSX_FILE_NAME,
        useStyles: true,
      };
      const wb = testUtils.createTestBook(
        new Excel.stream.xlsx.WorkbookWriter(options),
        'xlsx'
      );

      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          testUtils.checkTestBook(wb2, 'xlsx');
        });
    });

    it('shared formula', () => {
      const options = {
        filename: TEST_XLSX_FILE_NAME,
        useStyles: false,
      };
      const wb = new Excel.stream.xlsx.WorkbookWriter(options);
      const ws = wb.addWorksheet('Hello');
      ws.getCell('A1').value = { formula: 'ROW()+COLUMN()', result: 2 };
      ws.getCell('B1').value = { sharedFormula: 'A1', result: 3 };
      ws.getCell('A2').value = { sharedFormula: 'A1', result: 3 };
      ws.getCell('B2').value = { sharedFormula: 'A1', result: 4 };

      ws.commit();
      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          const ws2 = wb2.getWorksheet('Hello');
          expect(ws2.getCell('A1').value).to.deep.equal({
            formula: 'ROW()+COLUMN()',
            result: 2,
          });
          expect(ws2.getCell('B1').value).to.deep.equal({
            sharedFormula: 'A1',
            result: 3,
          });
          expect(ws2.getCell('A2').value).to.deep.equal({
            sharedFormula: 'A1',
            result: 3,
          });
          expect(ws2.getCell('B2').value).to.deep.equal({
            sharedFormula: 'A1',
            result: 4,
          });
        });
    });

    it('auto filter', () => {
      const options = {
        filename: TEST_XLSX_FILE_NAME,
        useStyles: false,
      };
      const wb = new Excel.stream.xlsx.WorkbookWriter(options);
      const ws = wb.addWorksheet('Hello');
      ws.getCell('A1').value = 1;
      ws.getCell('B1').value = 1;
      ws.getCell('A2').value = 2;
      ws.getCell('B2').value = 2;
      ws.getCell('A3').value = 3;
      ws.getCell('B3').value = 3;

      ws.autoFilter = 'A1:B1';
      ws.commit();

      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          const ws2 = wb2.getWorksheet('Hello');
          expect(ws2.autoFilter).to.equal('A1:B1');
        });
    });

    it('Without styles', () => {
      const options = {
        filename: TEST_XLSX_FILE_NAME,
        useStyles: false,
      };
      const wb = testUtils.createTestBook(
        new Excel.stream.xlsx.WorkbookWriter(options),
        'xlsx'
      );

      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          testUtils.checkTestBook(wb2, 'xlsx', undefined, {
            checkStyles: false,
          });
        });
    });

    it('serializes row styles and columns properly', () => {
      const options = {
        filename: TEST_XLSX_FILE_NAME,
        useStyles: true,
      };
      const wb = new Excel.stream.xlsx.WorkbookWriter(options);
      const ws = wb.addWorksheet('blort');

      const colStyle = {
        font: testUtils.styles.fonts.comicSansUdB16,
        alignment: testUtils.styles.namedAlignments.middleCentre,
      };
      ws.columns = [
        { header: 'A1', width: 10 },
        { header: 'B1', style: colStyle },
        { header: 'C1', width: 30 },
        { header: 'D1' },
      ];

      ws.getRow(2).font = testUtils.styles.fonts.broadwayRedOutline20;

      ws.getCell('A2').value = 'A2';
      ws.getCell('B2').value = 'B2';
      ws.getCell('C2').value = 'C2';
      ws.getCell('A3').value = 'A3';
      ws.getCell('B3').value = 'B3';
      ws.getCell('C3').value = 'C3';

      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          const ws2 = wb2.getWorksheet('blort');
          ['A1', 'B1', 'C1', 'A2', 'B2', 'C2', 'A3', 'B3', 'C3'].forEach(
            address => {
              expect(ws2.getCell(address).value).to.equal(address);
            }
          );
          expect(ws2.getCell('B1').font).to.deep.equal(
            testUtils.styles.fonts.comicSansUdB16
          );
          expect(ws2.getCell('B1').alignment).to.deep.equal(
            testUtils.styles.namedAlignments.middleCentre
          );
          expect(ws2.getCell('A2').font).to.deep.equal(
            testUtils.styles.fonts.broadwayRedOutline20
          );
          expect(ws2.getCell('B2').font).to.deep.equal(
            testUtils.styles.fonts.broadwayRedOutline20
          );
          expect(ws2.getCell('C2').font).to.deep.equal(
            testUtils.styles.fonts.broadwayRedOutline20
          );
          expect(ws2.getCell('B3').font).to.deep.equal(
            testUtils.styles.fonts.comicSansUdB16
          );
          expect(ws2.getCell('B3').alignment).to.deep.equal(
            testUtils.styles.namedAlignments.middleCentre
          );

          expect(ws2.getColumn(2).font).to.deep.equal(
            testUtils.styles.fonts.comicSansUdB16
          );
          expect(ws2.getColumn(2).alignment).to.deep.equal(
            testUtils.styles.namedAlignments.middleCentre
          );
          expect(ws2.getColumn(2).width).to.equal(undefined);

          expect(ws2.getColumn(4).width).to.equal(undefined);

          expect(ws2.getRow(2).font).to.deep.equal(
            testUtils.styles.fonts.broadwayRedOutline20
          );
        });
    });

    it('rich text', () => {
      const options = {
        filename: TEST_XLSX_FILE_NAME,
        useStyles: true,
      };
      const wb = new Excel.stream.xlsx.WorkbookWriter(options);
      const ws = wb.addWorksheet('Hello');

      ws.getCell('A1').value = {
        richText: [
          {
            font: { color: { argb: 'FF0000' } },
            text: 'red ',
          },
          {
            font: { color: { argb: '00FF00' }, bold: true },
            text: ' bold green',
          },
        ],
      };

      ws.getCell('B1').value = 'plain text';

      ws.commit();
      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          const ws2 = wb2.getWorksheet('Hello');
          expect(ws2.getCell('A1').value).to.deep.equal({
            richText: [
              {
                font: { color: { argb: 'FF0000' } },
                text: 'red ',
              },
              {
                font: { color: { argb: '00FF00' }, bold: true },
                text: ' bold green',
              },
            ],
          });
          expect(ws2.getCell('B1').value).to.equal('plain text');
        });
    });

    it('A lot of sheets', function() {
      this.timeout(5000);

      let i;
      const wb = new Excel.stream.xlsx.WorkbookWriter({
        filename: TEST_XLSX_FILE_NAME,
      });
      const numSheets = 90;
      // add numSheets sheets
      for (i = 1; i <= numSheets; i++) {
        const ws = wb.addWorksheet(`sheet${i}`);
        ws.getCell('A1').value = i;
      }
      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          for (i = 1; i <= numSheets; i++) {
            const ws2 = wb2.getWorksheet(`sheet${i}`);
            expect(ws2).to.be.ok();
            expect(ws2.getCell('A1').value).to.equal(i);
          }
        });
    });

    it('addRow', () => {
      const options = {
        stream: fs.createWriteStream(TEST_XLSX_FILE_NAME, { flags: 'w' }),
        useStyles: true,
        useSharedStrings: true,
      };
      const workbook = new Excel.stream.xlsx.WorkbookWriter(options);
      const worksheet = workbook.addWorksheet('test');
      const newRow = worksheet.addRow(['hello']);
      newRow.commit();
      worksheet.commit();
      return workbook.commit();
    });

    it('defined names', () => {
      const wb = new Excel.stream.xlsx.WorkbookWriter({
        filename: TEST_XLSX_FILE_NAME,
      });
      const ws = wb.addWorksheet('blort');
      ws.getCell('A1').value = 5;
      ws.getCell('A1').name = 'five';

      ws.getCell('A3').value = 'drei';
      ws.getCell('A3').name = 'threes';
      ws.getCell('B3').value = 'trois';
      ws.getCell('B3').name = 'threes';
      ws.getCell('B3').value = 'san';
      ws.getCell('B3').name = 'threes';

      ws.getCell('E1').value = 'grün';
      ws.getCell('E1').name = 'greens';
      ws.getCell('E2').value = 'vert';
      ws.getCell('E2').name = 'greens';
      ws.getCell('E3').value = 'verde';
      ws.getCell('E3').name = 'greens';

      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          const ws2 = wb2.getWorksheet('blort');
          expect(ws2.getCell('A1').name).to.equal('five');

          expect(ws2.getCell('A3').name).to.equal('threes');
          expect(ws2.getCell('B3').name).to.equal('threes');
          expect(ws2.getCell('B3').name).to.equal('threes');

          expect(ws2.getCell('E1').name).to.equal('greens');
          expect(ws2.getCell('E2').name).to.equal('greens');
          expect(ws2.getCell('E3').name).to.equal('greens');
        });
    });

    it('does not escape special xml characters', () => {
      const wb = new Excel.stream.xlsx.WorkbookWriter({
        filename: TEST_XLSX_FILE_NAME,
        useSharedStrings: true,
      });
      const ws = wb.addWorksheet('blort');
      const xmlCharacters = 'xml characters: & < > "';

      ws.getCell('A1').value = xmlCharacters;

      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          const ws2 = wb2.getWorksheet('blort');
          expect(ws2.getCell('A1').value).to.equal(xmlCharacters);
        });
    });

    it('serializes and deserializes dataValidations', () => {
      const options = { filename: TEST_XLSX_FILE_NAME };
      const wb = testUtils.createTestBook(
        new Excel.stream.xlsx.WorkbookWriter(options),
        'xlsx',
        ['dataValidations']
      );

      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          testUtils.checkTestBook(wb2, 'xlsx', ['dataValidations']);
        });
    });

    it('with zip compression option', () => {
      const options = {
        filename: TEST_XLSX_FILE_NAME,
        useStyles: true,
        zip: {
          zlib: { level: 9 },// Sets the compression level.
        },
      };
      const wb = testUtils.createTestBook(
        new Excel.stream.xlsx.WorkbookWriter(options),
        'xlsx',
        ['dataValidations']
      );

      return wb
        .commit()
        .then(() => {
          const wb2 = new Excel.Workbook();
          return wb2.xlsx.readFile(TEST_XLSX_FILE_NAME);
        })
        .then(wb2 => {
          testUtils.checkTestBook(wb2, 'xlsx', ['dataValidations']);
        });
    });
  });
});
<|endoftext|>


<commit_before>/*global define*/

define([
    'underscore',
    'backbone',
    'models/application-model'
], function (_, Backbone, ApplicationModel) {
    'use strict';

    var ApplicationCollection = Backbone.Collection.extend({
        model: ApplicationModel
    });

    return ApplicationCollection;
});<commit_msg>Cleanup: We never used the 'application' collection.<commit_after><|endoftext|>


<commit_before>/* http://github.com/mindmup/bootstrap-wysiwyg */
/*global jQuery, $, FileReader*/
/*jslint browser:true*/
(function ($) {
	'use strict';
	var readFileIntoDataUrl = function (fileInfo) {
		var loader = $.Deferred(),
			fReader = new FileReader();
		fReader.onload = function (e) {
			loader.resolve(e.target.result);
		};
		fReader.onerror = loader.reject;
		fReader.onprogress = loader.notify;
		fReader.readAsDataURL(fileInfo);
		return loader.promise();
	};
	$.fn.cleanHtml = function () {
		var html = $(this).html();
		return html && html.replace(/(
|\s|

<\/div>| )*$/, '');
	};
	$.fn.wysiwyg = function (userOptions) {
		var editor = this,
			selectedRange,
			options,
			toolbarBtnSelector,
			updateToolbar = function () {
				if (options.activeToolbarClass) {
					$(options.toolbarSelector).find(toolbarBtnSelector).each(function () {
						var command = $(this).data(options.commandRole);
						if (document.queryCommandState(command)) {
							$(this).addClass(options.activeToolbarClass);
						} else {
							$(this).removeClass(options.activeToolbarClass);
						}
					});
				}
			},
			execCommand = function (commandWithArgs, valueArg) {
				var commandArr = commandWithArgs.split(' '),
					command = commandArr.shift(),
					args = commandArr.join(' ') + (valueArg || '');
				document.execCommand(command, 0, args);
				updateToolbar();
			},
			bindHotkeys = function (hotKeys) {
				$.each(hotKeys, function (hotkey, command) {
					editor.keydown(hotkey, function (e) {
						if (editor.attr('contenteditable') && editor.is(':visible')) {
							e.preventDefault();
							e.stopPropagation();
							execCommand(command);
						}
					}).keyup(hotkey, function (e) {
						if (editor.attr('contenteditable') && editor.is(':visible')) {
							e.preventDefault();
							e.stopPropagation();
						}
					});
				});
			},
			getCurrentRange = function () {
				var sel = window.getSelection();
				if (sel.getRangeAt && sel.rangeCount) {
					return sel.getRangeAt(0);
				}
			},
			saveSelection = function () {
				selectedRange = getCurrentRange();
			},
			restoreSelection = function () {
				var selection = window.getSelection();
				if (selectedRange) {
					try {
						selection.removeAllRanges();
					} catch (ex) {
						document.body.createTextRange().select();
						document.selection.empty();
					}

					selection.addRange(selectedRange);
				}
			},
			insertFiles = function (files) {
				editor.focus();
				$.each(files, function (idx, fileInfo) {
					if (/^image\//.test(fileInfo.type)) {
						$.when(readFileIntoDataUrl(fileInfo)).done(function (dataUrl) {
							execCommand('insertimage', dataUrl);
						}).fail(function (e) {
							options.fileUploadError("file-reader", e);
						});
					} else {
						options.fileUploadError("unsupported-file-type", fileInfo.type);
					}
				});
			},
			markSelection = function (input, color) {
				restoreSelection();
				if (document.queryCommandSupported('hiliteColor')) {
					document.execCommand('hiliteColor', 0, color || 'transparent');
				}
				saveSelection();
				input.data(options.selectionMarker, color);
			},
			bindToolbar = function (toolbar, options) {
				toolbar.find(toolbarBtnSelector).click(function () {
					restoreSelection();
					editor.focus();
					execCommand($(this).data(options.commandRole));
					saveSelection();
				});
				toolbar.find('[data-toggle=dropdown]').click(restoreSelection);

				toolbar.find('input[type=text][data-' + options.commandRole + ']').on('webkitspeechchange change', function () {
					var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */
					this.value = '';
					restoreSelection();
					if (newValue) {
						editor.focus();
						execCommand($(this).data(options.commandRole), newValue);
					}
					saveSelection();
				}).on('focus', function () {
					var input = $(this);
					if (!input.data(options.selectionMarker)) {
						markSelection(input, options.selectionColor);
						input.focus();
					}
				}).on('blur', function () {
					var input = $(this);
					if (input.data(options.selectionMarker)) {
						markSelection(input, false);
					}
				});
				toolbar.find('input[type=file][data-' + options.commandRole + ']').change(function () {
					restoreSelection();
					if (this.type === 'file' && this.files && this.files.length > 0) {
						insertFiles(this.files);
					}
					saveSelection();
					this.value = '';
				});
			},
			initFileDrops = function () {
				editor.on('dragenter dragover', false)
					.on('drop', function (e) {
						var dataTransfer = e.originalEvent.dataTransfer;
						e.stopPropagation();
						e.preventDefault();
						if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
							insertFiles(dataTransfer.files);
						}
					});
			};
		options = $.extend({}, $.fn.wysiwyg.defaults, userOptions);
		toolbarBtnSelector = 'a[data-' + options.commandRole + '],button[data-' + options.commandRole + '],input[type=button][data-' + options.commandRole + ']';
		bindHotkeys(options.hotKeys);
		if (options.dragAndDropImages) {
			initFileDrops();
		}
		bindToolbar($(options.toolbarSelector), options);
		editor.attr('contenteditable', true)
			.on('mouseup keyup mouseout', function () {
				saveSelection();
				updateToolbar();
			});
		$(window).bind('touchend', function (e) {
			var isInside = (editor.is(e.target) || editor.has(e.target).length > 0),
				currentRange = getCurrentRange(),
				clear = currentRange && (currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset);
			if (!clear || isInside) {
				saveSelection();
				updateToolbar();
			}
		});
		return this;
	};
	$.fn.wysiwyg.defaults = {
		hotKeys: {
			'ctrl+b meta+b': 'bold',
			'ctrl+i meta+i': 'italic',
			'ctrl+u meta+u': 'underline',
			'ctrl+z meta+z': 'undo',
			'ctrl+y meta+y meta+shift+z': 'redo',
			'ctrl+l meta+l': 'justifyleft',
			'ctrl+r meta+r': 'justifyright',
			'ctrl+e meta+e': 'justifycenter',
			'ctrl+j meta+j': 'justifyfull',
			'shift+tab': 'outdent',
			'tab': 'indent'
		},
		toolbarSelector: '[data-role=editor-toolbar]',
		commandRole: 'edit',
		activeToolbarClass: 'btn-info',
		selectionMarker: 'edit-focus-marker',
		selectionColor: 'darkgrey',
		dragAndDropImages: true,
		fileUploadError: function (reason, detail) { console.log("File upload error", reason, detail); }
	};
}(window.jQuery));
<commit_msg>Changed updateToolbar to support commands with arguments.<commit_after>/* http://github.com/mindmup/bootstrap-wysiwyg */
/*global jQuery, $, FileReader*/
/*jslint browser:true*/
(function ($) {
	'use strict';
	var readFileIntoDataUrl = function (fileInfo) {
		var loader = $.Deferred(),
			fReader = new FileReader();
		fReader.onload = function (e) {
			loader.resolve(e.target.result);
		};
		fReader.onerror = loader.reject;
		fReader.onprogress = loader.notify;
		fReader.readAsDataURL(fileInfo);
		return loader.promise();
	};
	$.fn.cleanHtml = function () {
		var html = $(this).html();
		return html && html.replace(/(
|\s|

<\/div>| )*$/, '');
	};
	$.fn.wysiwyg = function (userOptions) {
		var editor = this,
			selectedRange,
			options,
			toolbarBtnSelector,
			updateToolbar = function () {
				if (options.activeToolbarClass) {
					$(options.toolbarSelector).find(toolbarBtnSelector).each(function () {
						var commandArr = $(this).data(options.commandRole).split(' '),
							command = commandArr[0];

						// If the command has an argument and its value matches this button
						if (commandArr.length > 1 && document.queryCommandEnabled(command) && document.queryCommandValue(command) === commandArr[1]) {
							$(this).addClass(options.activeToolbarClass);
						// Else if the command has no arguments and it is active
						} else if (commandArr.length === 1 && document.queryCommandEnabled(command) && document.queryCommandState(command)) {
							$(this).addClass(options.activeToolbarClass);
						// Else the command is not active
						} else {
							$(this).removeClass(options.activeToolbarClass);
						}
					});
				}
			},
			execCommand = function (commandWithArgs, valueArg) {
				var commandArr = commandWithArgs.split(' '),
					command = commandArr.shift(),
					args = commandArr.join(' ') + (valueArg || '');
				document.execCommand(command, 0, args);
				updateToolbar();
			},
			bindHotkeys = function (hotKeys) {
				$.each(hotKeys, function (hotkey, command) {
					editor.keydown(hotkey, function (e) {
						if (editor.attr('contenteditable') && editor.is(':visible')) {
							e.preventDefault();
							e.stopPropagation();
							execCommand(command);
						}
					}).keyup(hotkey, function (e) {
						if (editor.attr('contenteditable') && editor.is(':visible')) {
							e.preventDefault();
							e.stopPropagation();
						}
					});
				});
			},
			getCurrentRange = function () {
				var sel = window.getSelection();
				if (sel.getRangeAt && sel.rangeCount) {
					return sel.getRangeAt(0);
				}
			},
			saveSelection = function () {
				selectedRange = getCurrentRange();
			},
			restoreSelection = function () {
				var selection = window.getSelection();
				if (selectedRange) {
					try {
						selection.removeAllRanges();
					} catch (ex) {
						document.body.createTextRange().select();
						document.selection.empty();
					}

					selection.addRange(selectedRange);
				}
			},
			insertFiles = function (files) {
				editor.focus();
				$.each(files, function (idx, fileInfo) {
					if (/^image\//.test(fileInfo.type)) {
						$.when(readFileIntoDataUrl(fileInfo)).done(function (dataUrl) {
							execCommand('insertimage', dataUrl);
						}).fail(function (e) {
							options.fileUploadError("file-reader", e);
						});
					} else {
						options.fileUploadError("unsupported-file-type", fileInfo.type);
					}
				});
			},
			markSelection = function (input, color) {
				restoreSelection();
				if (document.queryCommandSupported('hiliteColor')) {
					document.execCommand('hiliteColor', 0, color || 'transparent');
				}
				saveSelection();
				input.data(options.selectionMarker, color);
			},
			bindToolbar = function (toolbar, options) {
				toolbar.find(toolbarBtnSelector).click(function () {
					restoreSelection();
					editor.focus();
					execCommand($(this).data(options.commandRole));
					saveSelection();
				});
				toolbar.find('[data-toggle=dropdown]').click(restoreSelection);

				toolbar.find('input[type=text][data-' + options.commandRole + ']').on('webkitspeechchange change', function () {
					var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */
					this.value = '';
					restoreSelection();
					if (newValue) {
						editor.focus();
						execCommand($(this).data(options.commandRole), newValue);
					}
					saveSelection();
				}).on('focus', function () {
					var input = $(this);
					if (!input.data(options.selectionMarker)) {
						markSelection(input, options.selectionColor);
						input.focus();
					}
				}).on('blur', function () {
					var input = $(this);
					if (input.data(options.selectionMarker)) {
						markSelection(input, false);
					}
				});
				toolbar.find('input[type=file][data-' + options.commandRole + ']').change(function () {
					restoreSelection();
					if (this.type === 'file' && this.files && this.files.length > 0) {
						insertFiles(this.files);
					}
					saveSelection();
					this.value = '';
				});
			},
			initFileDrops = function () {
				editor.on('dragenter dragover', false)
					.on('drop', function (e) {
						var dataTransfer = e.originalEvent.dataTransfer;
						e.stopPropagation();
						e.preventDefault();
						if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
							insertFiles(dataTransfer.files);
						}
					});
			};
		options = $.extend({}, $.fn.wysiwyg.defaults, userOptions);
		toolbarBtnSelector = 'a[data-' + options.commandRole + '],button[data-' + options.commandRole + '],input[type=button][data-' + options.commandRole + ']';
		bindHotkeys(options.hotKeys);
		if (options.dragAndDropImages) {
			initFileDrops();
		}
		bindToolbar($(options.toolbarSelector), options);
		editor.attr('contenteditable', true)
			.on('mouseup keyup mouseout', function () {
				saveSelection();
				updateToolbar();
			});
		$(window).bind('touchend', function (e) {
			var isInside = (editor.is(e.target) || editor.has(e.target).length > 0),
				currentRange = getCurrentRange(),
				clear = currentRange && (currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset);
			if (!clear || isInside) {
				saveSelection();
				updateToolbar();
			}
		});
		return this;
	};
	$.fn.wysiwyg.defaults = {
		hotKeys: {
			'ctrl+b meta+b': 'bold',
			'ctrl+i meta+i': 'italic',
			'ctrl+u meta+u': 'underline',
			'ctrl+z meta+z': 'undo',
			'ctrl+y meta+y meta+shift+z': 'redo',
			'ctrl+l meta+l': 'justifyleft',
			'ctrl+r meta+r': 'justifyright',
			'ctrl+e meta+e': 'justifycenter',
			'ctrl+j meta+j': 'justifyfull',
			'shift+tab': 'outdent',
			'tab': 'indent'
		},
		toolbarSelector: '[data-role=editor-toolbar]',
		commandRole: 'edit',
		activeToolbarClass: 'btn-info',
		selectionMarker: 'edit-focus-marker',
		selectionColor: 'darkgrey',
		dragAndDropImages: true,
		fileUploadError: function (reason, detail) { console.log("File upload error", reason, detail); }
	};
}(window.jQuery));
<|endoftext|>


<commit_before>var location;
var dates;
var rating;

var bookRoom = function(session, args, next, builder) {

    location = builder.EntityRecognizer.findEntity(args.entities, 'location');
    dates = builder.EntityRecognizer.findAllEntities(args.entities, 'builtin.datetime.date');
    rating = builder.EntityRecognizer.findEntity(args.entities, 'rating');

    if (!location) {
        console.log("Location not present");
        builder.Prompts.text(session, "Please enter the city ?");
    } else {
        next({ response: location.entity });
    }

    // if (dates.length < 2) {
    //     console.log("Both dates not present");
    // }

    // if (!rating) {
    //     console.log("Rating not present");
    // }

};
var _bookRoom = function(session, results, builder) {
    location = results.response;
    console.log("You asked for", location);
    session.send("You asked for", location);
    // if (dates) {
    //     if (dates.length == 0) {
    //         session.dialogData.
    //         builder.Prompts.text(session, "Please provide checkin date ?");
    //     }
    // }
};

var getBooking = function(session, args, next, builder) {

};

module.exports = { bookRoom: bookRoom, _bookRoom: _bookRoom, getBooking: getBooking };<commit_msg>Minor fix<commit_after>var location;
var dates;
var rating;

var bookRoom = function(session, args, next, builder) {

    location = builder.EntityRecognizer.findEntity(args.entities, 'location');
    dates = builder.EntityRecognizer.findAllEntities(args.entities, 'builtin.datetime.date');
    rating = builder.EntityRecognizer.findEntity(args.entities, 'rating');

    if (!location) {
        console.log("Location not present");
        builder.Prompts.text(session, "Please enter the city ?");
    } else {
        next({ response: location.entity });
    }

    // if (dates.length < 2) {
    //     console.log("Both dates not present");
    // }

    // if (!rating) {
    //     console.log("Rating not present");
    // }

};
var _bookRoom = function(session, results, builder) {
    location = results.response;
    console.log("You asked for", location);
    session.send("You asked for " + location);
    // if (dates) {
    //     if (dates.length == 0) {
    //         session.dialogData.
    //         builder.Prompts.text(session, "Please provide checkin date ?");
    //     }
    // }
};

var getBooking = function(session, args, next, builder) {

};

module.exports = { bookRoom: bookRoom, _bookRoom: _bookRoom, getBooking: getBooking };<|endoftext|>


<commit_before>import AppContext from '../../AppContext';
import CloseButton from '../common/CloseButton';
import BackButton from '../common/BackButton';
import {useContext, useState} from 'react';
import Switch from '../common/Switch';
import {getSiteNewsletters} from '../../utils/helpers';
import ActionButton from '../common/ActionButton';

const React = require('react');

function AccountHeader() {
    const {brandColor, lastPage, onAction} = useContext(AppContext);
    return (

            <BackButton brandColor={brandColor} hidden={!lastPage} onClick={(e) => {
                onAction('back');
            }} />

Email preferences

    );
}

function NewsletterPrefSection({newsletter, subscribedNewsletters, setSubscribedNewsletters}) {
    const isChecked = subscribedNewsletters.some((d) => {
        return d.id === newsletter?.id;
    });
    const [description, setDescription] = useState(newsletter?.description || '');
    const [timeoutId, setTimeoutId] = useState(null);
    let finalDescription = description;
    if (!description) {
        finalDescription = isChecked ? 'Subscribed' : 'Unsubscribed';
    }
    return (

{newsletter.name}

{finalDescription}

                <Switch id={newsletter.id} onToggle={(e, checked) => {
                    let updatedNewsletters = [];
                    if (!checked) {
                        updatedNewsletters = subscribedNewsletters.filter((d) => {
                            return d.id !== newsletter.id;
                        });
                        setDescription('Unsubscribed');
                        clearTimeout(timeoutId);
                        let newTimeoutId = setTimeout(() => {
                            setDescription(newsletter?.description);
                        }, 3000);
                        setTimeoutId(newTimeoutId);
                    } else {
                        updatedNewsletters = subscribedNewsletters.filter((d) => {
                            return d.id !== newsletter.id;
                        }).concat(newsletter);
                        setDescription('Subscribed');
                        clearTimeout(timeoutId);
                        let newTimeoutId = setTimeout(() => {
                            setDescription(newsletter?.description);
                        }, 3000);
                        setTimeoutId(newTimeoutId);
                    }
                    setSubscribedNewsletters(updatedNewsletters);
                }} checked={isChecked} />

    );
}

function NewsletterPrefs({subscribedNewsletters, setSubscribedNewsletters}) {
    const {site} = useContext(AppContext);
    const newsletters = getSiteNewsletters({site});
    return newsletters.map((newsletter) => {
        return (
            <NewsletterPrefSection
                key={newsletter?.id}
                newsletter={newsletter}
                subscribedNewsletters={subscribedNewsletters}
                setSubscribedNewsletters={setSubscribedNewsletters}
            />
        );
    });
}

function ShowPaidMemberMessage({site, isPaid}) {
    if (isPaid) {
        return (

Unsubscribing from emails will not cancel your paid subscription to {site?.title}

        );
    }
    return null;
}

export default function NewsletterManagement({
    subscribedNewsletters,
    updateSubscribedNewsletters,
    unsubscribeAll,
    isPaidMember
}) {
    const isDisabled = !subscribedNewsletters?.length;
    const {brandColor, site} = useContext(AppContext);
    return (

            <CloseButton />
            <AccountHeader />

                    <NewsletterPrefs
                        subscribedNewsletters={subscribedNewsletters}
                        setSubscribedNewsletters={(updatedNewsletters) => {
                            let newsletters = updatedNewsletters.map((d) => {
                                return {
                                    id: d.id
                                };
                            });
                            updateSubscribedNewsletters(newsletters);
                        }}
                    />

                    <ActionButton
                        isRunning={false}
                        onClick={(e) => {
                            unsubscribeAll();
                        }}
                        disabled={isDisabled}
                        brandColor={brandColor}
                        isPrimary={false}
                        label='Unsubscribe from all emails'
                        isDestructive={true}
                        style={{width: '100%'}}
                    />
                    <ShowPaidMemberMessage isPaid={isPaidMember} site={site} />

    );
}
<commit_msg>Refined newsletter preference update UI<commit_after>import AppContext from '../../AppContext';
import CloseButton from '../common/CloseButton';
import BackButton from '../common/BackButton';
import {useContext, useState} from 'react';
import Switch from '../common/Switch';
import {getSiteNewsletters} from '../../utils/helpers';
import ActionButton from '../common/ActionButton';

const React = require('react');

function AccountHeader() {
    const {brandColor, lastPage, onAction} = useContext(AppContext);
    return (

            <BackButton brandColor={brandColor} hidden={!lastPage} onClick={(e) => {
                onAction('back');
            }} />

Email preferences

    );
}

function SuccessIcon({show}) {
    if (!show) {
        return null;
    }
    return (

            ✅

    );
}

function NewsletterPrefSection({newsletter, subscribedNewsletters, setSubscribedNewsletters}) {
    const isChecked = subscribedNewsletters.some((d) => {
        return d.id === newsletter?.id;
    });

    const [showUpdated, setShowUpdated] = useState(false);
    const [timeoutId, setTimeoutId] = useState(null);
    return (

{newsletter.name}

{newsletter?.description}

                <SuccessIcon show={showUpdated} />
                <Switch id={newsletter.id} onToggle={(e, checked) => {
                    let updatedNewsletters = [];
                    if (!checked) {
                        updatedNewsletters = subscribedNewsletters.filter((d) => {
                            return d.id !== newsletter.id;
                        });
                        setShowUpdated(true);
                        clearTimeout(timeoutId);
                        let newTimeoutId = setTimeout(() => {
                            setShowUpdated(false);
                        }, 3000);
                        setTimeoutId(newTimeoutId);
                    } else {
                        updatedNewsletters = subscribedNewsletters.filter((d) => {
                            return d.id !== newsletter.id;
                        }).concat(newsletter);
                        setShowUpdated(true);
                        clearTimeout(timeoutId);
                        let newTimeoutId = setTimeout(() => {
                            setShowUpdated(false);
                        }, 3000);
                        setTimeoutId(newTimeoutId);
                    }
                    setSubscribedNewsletters(updatedNewsletters);
                }} checked={isChecked} />

    );
}

function NewsletterPrefs({subscribedNewsletters, setSubscribedNewsletters}) {
    const {site} = useContext(AppContext);
    const newsletters = getSiteNewsletters({site});
    return newsletters.map((newsletter) => {
        return (
            <NewsletterPrefSection
                key={newsletter?.id}
                newsletter={newsletter}
                subscribedNewsletters={subscribedNewsletters}
                setSubscribedNewsletters={setSubscribedNewsletters}
            />
        );
    });
}

function ShowPaidMemberMessage({site, isPaid}) {
    if (isPaid) {
        return (

Unsubscribing from emails will not cancel your paid subscription to {site?.title}

        );
    }
    return null;
}

export default function NewsletterManagement({
    subscribedNewsletters,
    updateSubscribedNewsletters,
    unsubscribeAll,
    isPaidMember
}) {
    const isDisabled = !subscribedNewsletters?.length;
    const {brandColor, site} = useContext(AppContext);
    return (

            <CloseButton />
            <AccountHeader />

                    <NewsletterPrefs
                        subscribedNewsletters={subscribedNewsletters}
                        setSubscribedNewsletters={(updatedNewsletters) => {
                            let newsletters = updatedNewsletters.map((d) => {
                                return {
                                    id: d.id
                                };
                            });
                            updateSubscribedNewsletters(newsletters);
                        }}
                    />

                    <ActionButton
                        isRunning={false}
                        onClick={(e) => {
                            unsubscribeAll();
                        }}
                        disabled={isDisabled}
                        brandColor={brandColor}
                        isPrimary={false}
                        label='Unsubscribe from all emails'
                        isDestructive={true}
                        style={{width: '100%'}}
                    />
                    <ShowPaidMemberMessage isPaid={isPaidMember} site={site} />

    );
}
<|endoftext|>


<commit_before>/* global Cache */

const path = require('path')
const fs = require('mz/fs')
const request = require('request-promise')
const VirtualGroups = require('./VirtualGroups.js')
const DiscordMember = require('./DiscordMember')
const Util = require('./Util')

// The default settings for DiscordServer.
const DefaultSettings = {
  verifiedRole: null,
  verifiedRemovedRole: null,
  nicknameUsers: true,
  joinDM: true,
  verifyChannel: null,
  announceChannel: null,
  nicknameFormat: '%USERNAME%',
  welcomeMessage: 'Welcome to %SERVER%, %USERNAME%!',
  groupRankBindings: []
}

/**
 * A DiscordServer class, it represents a guild that the bot is in.
 * @class DiscordServer
 */
class DiscordServer {
  /**
   * Creates an instance of DiscordServer.
   * @param {DiscordBot} discordBot The discord bot that the server belongs to.
   * @param {Snowflake} id The guild id
   */
  constructor (discordBot, id) {
    this.id = id
    this.discordBot = discordBot
    this.bot = this.discordBot.bot

    this.server = this.bot.guilds.get(id)

    // Load this server's settings.
    this.settings = {}
    this.areSettingsLoaded = false
    this.settingsPath = path.join(__dirname, 'data', `${id}.json`)

    // We will load the settings in DiscordBot.getServer in order to know when
    // the server is ready from the promise it returns.
    // this.loadSettings();
  }

  /**
   * This method loads the settings specific for this server.
   * It also creates a settings file if there isn't one.
   * @returns {undefined}
   * @memberof DiscordServer
   */
  async loadSettings () {
    // If there's no settings file for this server, create one.
    if (!await fs.exists(this.settingsPath)) {
      await fs.writeFile(this.settingsPath, JSON.stringify(DefaultSettings))
    }

    if (!await fs.exists(this.settingsPath)) {
      throw new Error(`Couldn't write settings file: ${this.settingsPath}`)
    }

    // Load the settings file.
    let fileData = await fs.readFile(this.settingsPath)

    if (this.areSettingsLoaded) return

    try {
      this.settings = JSON.parse(fileData)
      this.areSettingsLoaded = true
    } catch (e) {
      console.log(`${this.settingsPath} appears to be corrupted.`)
    }

    if (this.settings.commando != null && this.settings.commando.prefix != null) {
      this.server._commandPrefix = this.settings.commando.prefix
    }

    this.cleanupRankBindings()
  }

  /**
   * Clears the member cache for a specific Discord user.
   * @static
   * @param {Snowflake} id The Discord user id
   * @memberof DiscordServer
   */
  static async clearMemberCache (id) {
    await Cache.set('users', id, null)
  }

  /**
   * Returns a setting value. Tries the saved settings, then tries
   * the default settings.
   * @param {string} key The setting key to get
   * @returns {any} The setting value
   * @memberof DiscordServer
   */
  getSetting (key) {
    if (!this.areSettingsLoaded) {
      throw new Error('Attempt to get a setting from a server whose settings are not loaded')
    }

    if (typeof this.settings[key] !== 'undefined') {
      return this.settings[key]
    } else if (typeof DefaultSettings[key] !== 'undefined') {
      return DefaultSettings[key]
    } else {
      return null
    }
  }

  /**
   * Set a server setting and then save it to disk.
   * @param {string} key The setting key to set
   * @param {any} value The setting value to set
   * @memberof DiscordServer
   */
  setSetting (key, value) {
    if (!this.areSettingsLoaded) {
      throw new Error('Attempt to change a setting from a server whose settings are not loaded')
    }

    this.settings[key] = value

    fs.writeFileSync(this.settingsPath, JSON.stringify(this.settings))
  }

  /**
   * Converts a group binding from the old format to the new format.
   * @static
   * @param {object} binding The binding to convert in the old format
   * @returns {object} The binding in the new format
   * @memberof DiscordServer
   */
  static convertOldBinding (binding) {
    let newBinding = { role: binding.role }

    let ranks = []
    if (binding.operator === 'gt') {
      for (let i = binding.rank; i <= 255; i++) {
        ranks.push(i)
      }
    } else if (binding.operator === 'lt') {
      for (let i = 1; i <= binding.rank; i++) {
        ranks.push(i)
      }
    } else {
      ranks.push(binding.rank)
    }

    newBinding.groups = [
      {
        id: binding.group,
        ranks
      }
    ]

    return newBinding
  }

  /**
   * Checks if a group rank binding passes or fails for
   * a specific Roblox user.
   * @static
   * @param {object} binding The group binding
   * @param {int} userid The roblox user id
   * @param {string} username The roblox username
   * @returns {boolean} The group binding resolution
   * @memberof DiscordServer
   */
  static async resolveGroupRankBinding (binding, userid, username) {
    if (binding.group != null) {
      binding = this.convertOldBinding(binding)
    }

    let bindingHash = Util.md5(JSON.stringify(binding))

    // Check if the return value of this method has already been
    // cached in memory. If so, return that.
    let cachedBinding = await Cache.get(`bindings.${userid}`, bindingHash)
    if (cachedBinding !== null) {
      return cachedBinding
    }

    let returnValue = false

    for (let group of binding.groups) {
      if (VirtualGroups[group.id]) {
        // If this group is a virtual group, then execute that function instead.
        // 'all' is remapped to >1. Since this is the equivalent of no argument, we set it to null here.
        returnValue = await VirtualGroups[group.id]({id: userid, username}, group.ranks[0])
      } else {
        // Check the rank of the user in the Roblox group.
        let rank = await Cache.get(`groupranks.${userid}`, group.id)
        if (!rank){

          let responsestuff = await request({
              uri: `http://api.roblox.com/users/${userid}/groups`,
              json: true
          })

          if (!response) {
            throw new Error('Group rank HTTP request is malformed or in unknown format')
          }

          rank = 0
          for (let groupObj of responsestuff) {
            if (groupObj.Id === group.id) {
              rank = groupObj.Rank
            }
          }

          Cache.set(`groupranks.${userid}`, group.id, rank)
        }

        returnValue = group.ranks.includes(rank)

        if (returnValue) break
      }
    }

    // Cache the return value in memory.
    Cache.set(`bindings.${userid}`, bindingHash, returnValue)

    return returnValue
  }

  /**
   * Deletes a group rank binding associated with a role id.
   * @param {Snowflake} roleid The role id to clear all bindings that are associated with it.
   * @memberof DiscordServer
   */
  deleteGroupRankBinding (roleid) {
    if (roleid === 'all') {
      return this.setSetting('groupRankBindings', [])
    }

    let rankBindings = this.getSetting('groupRankBindings')

    for (let i = 0; i < rankBindings.length; i++) {
      let binding = rankBindings[i]

      if (binding.role === roleid || roleid === 'all') {
        rankBindings.splice(i, 1)
      }
    }

    this.setSetting('groupRankBindings', rankBindings)
  }

  /**
   * Deletes group bindings that are associated with Discord roles
   * that have been deleted.
   * @param {Channel} [noticeChannel] The channel to post the deletion notice to in a last resort if the owner is unreachable
   * @memberof DiscordServer
   */
  cleanupRankBindings (lastResortChannel) {
    let verifiedRole = this.getSetting('verifiedRole')
    let unverifiedRole = this.getSetting('verifiedRemovedRole')

    if (verifiedRole && !this.server.roles.get(verifiedRole)) {
      this.setSetting('verifiedRole', null)
      this.announce('Verified Role Deleted', 'Heads up! Looks like you (or someone) has deleted the verified role, so users will no longer get that when they verify.', { important: true, lastResortChannel })
    }

    if (unverifiedRole && !this.server.roles.get(unverifiedRole)) {
      this.setSetting('verifiedRemovedRole', null)
      this.announce('Unverified Role Deleted', 'Heads up! Looks like you (or someone) has deleted the unverified role, so unverified users will no longer receive that role.', { important: true, lastResortChannel })
    }

    for (let binding of this.getSetting('groupRankBindings')) {
      let id = binding.role
      if (!this.server.roles.get(id)) {
        this.deleteGroupRankBinding(id)

        this.announce('Bound Role Deleted', 'Heads up! Looks like you (or someone) has deleted a Discord role that was bound to ' + Util.getBindingText(binding, true), { important: true, lastResortChannel })
      }
    }
  }

  /**
   * Posts an announcement in the server's configured announcement channel.
   * If it's important, the server owner will be notified.
   * @param {string} title The title of the embed message
   * @param {string} text The body of the embed message
   * @param {object} [options={}] Options for the announcement
   * @param {boolean} options.important If this is an important announcement, the server owner will be mentioned or DM'd if no announce channel is set.
   * @memberof DiscordServer
   */
  async announce (title, text, options = {}) {
    let embed = {
      color: options.important ? 0xe74c3c : 0x0064ba,
      title,
      description: text
    }

    if (this.getSetting('announceChannel')) {
      let channel = await this.server.channels.get(this.getSetting('announceChannel'))

      if (channel) {
        try {
          await channel.send(options.important ? `${this.server.owner}` : '', { embed })
          return
        } catch (e) {}
      }
    }

    if (options.important) {
      try {
        await this.server.owner.send(`An important notice was triggered in your server "${this.server.name}" and there is no announcement channel configured, so it has been sent to you here:`, { embed })
        return
      } catch (e) {}

      if (this.server.systemChannel) {
        try {
          await this.server.systemChannel.send(`${this.server.owner} An important notice was triggered and there is no announcement channel configured and the server owner will not accept DMs from RoVer, so it has been posted here:`, { embed })
          return
        } catch (e) {}
      }

      if (options.lastResortChannel) {
        try {
          await options.lastResortChannel.send(`${this.server.owner} An important notice was triggered and there is no announcement channel configured and the server owner will not accept DMs from RoVer, so it has been posted here as a last resort:`, { embed })
        } catch (e) {}
      }
    }
  }

  /**
   * Gets this server's specific welcome message, or the default
   * one if none is configured.
   * @param {object} data String replacement data
   * @returns {string} The processed string
   * @memberof DiscordServer
   */
  getWelcomeMessage (data, member) {
    return Util.formatDataString(this.getSetting('welcomeMessage'), data, member)
  }

  /**
   * Checks to see if this server has configured a custom welcome message.
   * @returns {boolean} True if this server has a custom welcome message
   * @memberof DiscordServer
   */
  hasCustomWelcomeMessage () {
    return DefaultSettings.welcomeMessage !== this.getSetting('welcomeMessage')
  }

  /**
   * Returns a DiscordMember belonging to this DiscordServer.
   * @param {Snowflake} id The id of the user
   * @returns {Promise<DiscordMember>} The new discord member
   * @memberof DiscordServer
   */
  async getMember (id) {
    return DiscordMember.new(this, id)
  }

  /**
   * Returns whether or not a role is in use by the verified, not verified, or binding roles
   *
   * @param {Snowflake} id The role id
   * @returns {boolean} True if the role is in use
   * @memberof DiscordServer
   */
  isRoleInUse (id) {
    if (this.getSetting('verifiedRole') === id) return true
    if (this.getSetting('verifiedRemovedRole') === id) return true

    for (let binding of this.getSetting('groupRankBindings')) {
      if (binding.role === id) return true
    }

    return false
  }
}

module.exports = DiscordServer
<commit_msg>Cached groups instead of ranks & made variable names more relevant<commit_after>/* global Cache */

const path = require('path')
const fs = require('mz/fs')
const request = require('request-promise')
const VirtualGroups = require('./VirtualGroups.js')
const DiscordMember = require('./DiscordMember')
const Util = require('./Util')

// The default settings for DiscordServer.
const DefaultSettings = {
  verifiedRole: null,
  verifiedRemovedRole: null,
  nicknameUsers: true,
  joinDM: true,
  verifyChannel: null,
  announceChannel: null,
  nicknameFormat: '%USERNAME%',
  welcomeMessage: 'Welcome to %SERVER%, %USERNAME%!',
  groupRankBindings: []
}

/**
 * A DiscordServer class, it represents a guild that the bot is in.
 * @class DiscordServer
 */
class DiscordServer {
  /**
   * Creates an instance of DiscordServer.
   * @param {DiscordBot} discordBot The discord bot that the server belongs to.
   * @param {Snowflake} id The guild id
   */
  constructor (discordBot, id) {
    this.id = id
    this.discordBot = discordBot
    this.bot = this.discordBot.bot

    this.server = this.bot.guilds.get(id)

    // Load this server's settings.
    this.settings = {}
    this.areSettingsLoaded = false
    this.settingsPath = path.join(__dirname, 'data', `${id}.json`)

    // We will load the settings in DiscordBot.getServer in order to know when
    // the server is ready from the promise it returns.
    // this.loadSettings();
  }

  /**
   * This method loads the settings specific for this server.
   * It also creates a settings file if there isn't one.
   * @returns {undefined}
   * @memberof DiscordServer
   */
  async loadSettings () {
    // If there's no settings file for this server, create one.
    if (!await fs.exists(this.settingsPath)) {
      await fs.writeFile(this.settingsPath, JSON.stringify(DefaultSettings))
    }

    if (!await fs.exists(this.settingsPath)) {
      throw new Error(`Couldn't write settings file: ${this.settingsPath}`)
    }

    // Load the settings file.
    let fileData = await fs.readFile(this.settingsPath)

    if (this.areSettingsLoaded) return

    try {
      this.settings = JSON.parse(fileData)
      this.areSettingsLoaded = true
    } catch (e) {
      console.log(`${this.settingsPath} appears to be corrupted.`)
    }

    if (this.settings.commando != null && this.settings.commando.prefix != null) {
      this.server._commandPrefix = this.settings.commando.prefix
    }

    this.cleanupRankBindings()
  }

  /**
   * Clears the member cache for a specific Discord user.
   * @static
   * @param {Snowflake} id The Discord user id
   * @memberof DiscordServer
   */
  static async clearMemberCache (id) {
    await Cache.set('users', id, null)
  }

  /**
   * Returns a setting value. Tries the saved settings, then tries
   * the default settings.
   * @param {string} key The setting key to get
   * @returns {any} The setting value
   * @memberof DiscordServer
   */
  getSetting (key) {
    if (!this.areSettingsLoaded) {
      throw new Error('Attempt to get a setting from a server whose settings are not loaded')
    }

    if (typeof this.settings[key] !== 'undefined') {
      return this.settings[key]
    } else if (typeof DefaultSettings[key] !== 'undefined') {
      return DefaultSettings[key]
    } else {
      return null
    }
  }

  /**
   * Set a server setting and then save it to disk.
   * @param {string} key The setting key to set
   * @param {any} value The setting value to set
   * @memberof DiscordServer
   */
  setSetting (key, value) {
    if (!this.areSettingsLoaded) {
      throw new Error('Attempt to change a setting from a server whose settings are not loaded')
    }

    this.settings[key] = value

    fs.writeFileSync(this.settingsPath, JSON.stringify(this.settings))
  }

  /**
   * Converts a group binding from the old format to the new format.
   * @static
   * @param {object} binding The binding to convert in the old format
   * @returns {object} The binding in the new format
   * @memberof DiscordServer
   */
  static convertOldBinding (binding) {
    let newBinding = { role: binding.role }

    let ranks = []
    if (binding.operator === 'gt') {
      for (let i = binding.rank; i <= 255; i++) {
        ranks.push(i)
      }
    } else if (binding.operator === 'lt') {
      for (let i = 1; i <= binding.rank; i++) {
        ranks.push(i)
      }
    } else {
      ranks.push(binding.rank)
    }

    newBinding.groups = [
      {
        id: binding.group,
        ranks
      }
    ]

    return newBinding
  }

  /**
   * Checks if a group rank binding passes or fails for
   * a specific Roblox user.
   * @static
   * @param {object} binding The group binding
   * @param {int} userid The roblox user id
   * @param {string} username The roblox username
   * @returns {boolean} The group binding resolution
   * @memberof DiscordServer
   */
  static async resolveGroupRankBinding (binding, userid, username) {
    if (binding.group != null) {
      binding = this.convertOldBinding(binding)
    }

    let bindingHash = Util.md5(JSON.stringify(binding))

    // Check if the return value of this method has already been
    // cached in memory. If so, return that.
    let cachedBinding = await Cache.get(`bindings.${userid}`, bindingHash)
    if (cachedBinding !== null) {
      return cachedBinding
    }

    let returnValue = false

    for (let group of binding.groups) {
      if (VirtualGroups[group.id]) {
        // If this group is a virtual group, then execute that function instead.
        // 'all' is remapped to >1. Since this is the equivalent of no argument, we set it to null here.
        returnValue = await VirtualGroups[group.id]({id: userid, username}, group.ranks[0])
      } else {
        // Check the rank of the user in the Roblox group.
        let groups = await Cache.get('playergroups', userid)
        let rank = 0
        if (!groups){

          groups = await request({
              uri: `http://api.roblox.com/users/${userid}/groups`,
              json: true
          })

          if (!groups) {
            throw new Error('Group rank HTTP request is malformed or in unknown format')
          }

          Cache.set('playergroups', userid, groups)
        }

        for (let groupObj of groups) {
          if (groupObj.Id.toString() === group.id) {
            returnValue = group.ranks.includes(groupObj.Rank)
            break
          }
        }

        if (returnValue) break
      }
    }

    // Cache the return value in memory.
    Cache.set(`bindings.${userid}`, bindingHash, returnValue)

    return returnValue
  }

  /**
   * Deletes a group rank binding associated with a role id.
   * @param {Snowflake} roleid The role id to clear all bindings that are associated with it.
   * @memberof DiscordServer
   */
  deleteGroupRankBinding (roleid) {
    if (roleid === 'all') {
      return this.setSetting('groupRankBindings', [])
    }

    let rankBindings = this.getSetting('groupRankBindings')

    for (let i = 0; i < rankBindings.length; i++) {
      let binding = rankBindings[i]

      if (binding.role === roleid || roleid === 'all') {
        rankBindings.splice(i, 1)
      }
    }

    this.setSetting('groupRankBindings', rankBindings)
  }

  /**
   * Deletes group bindings that are associated with Discord roles
   * that have been deleted.
   * @param {Channel} [noticeChannel] The channel to post the deletion notice to in a last resort if the owner is unreachable
   * @memberof DiscordServer
   */
  cleanupRankBindings (lastResortChannel) {
    let verifiedRole = this.getSetting('verifiedRole')
    let unverifiedRole = this.getSetting('verifiedRemovedRole')

    if (verifiedRole && !this.server.roles.get(verifiedRole)) {
      this.setSetting('verifiedRole', null)
      this.announce('Verified Role Deleted', 'Heads up! Looks like you (or someone) has deleted the verified role, so users will no longer get that when they verify.', { important: true, lastResortChannel })
    }

    if (unverifiedRole && !this.server.roles.get(unverifiedRole)) {
      this.setSetting('verifiedRemovedRole', null)
      this.announce('Unverified Role Deleted', 'Heads up! Looks like you (or someone) has deleted the unverified role, so unverified users will no longer receive that role.', { important: true, lastResortChannel })
    }

    for (let binding of this.getSetting('groupRankBindings')) {
      let id = binding.role
      if (!this.server.roles.get(id)) {
        this.deleteGroupRankBinding(id)

        this.announce('Bound Role Deleted', 'Heads up! Looks like you (or someone) has deleted a Discord role that was bound to ' + Util.getBindingText(binding, true), { important: true, lastResortChannel })
      }
    }
  }

  /**
   * Posts an announcement in the server's configured announcement channel.
   * If it's important, the server owner will be notified.
   * @param {string} title The title of the embed message
   * @param {string} text The body of the embed message
   * @param {object} [options={}] Options for the announcement
   * @param {boolean} options.important If this is an important announcement, the server owner will be mentioned or DM'd if no announce channel is set.
   * @memberof DiscordServer
   */
  async announce (title, text, options = {}) {
    let embed = {
      color: options.important ? 0xe74c3c : 0x0064ba,
      title,
      description: text
    }

    if (this.getSetting('announceChannel')) {
      let channel = await this.server.channels.get(this.getSetting('announceChannel'))

      if (channel) {
        try {
          await channel.send(options.important ? `${this.server.owner}` : '', { embed })
          return
        } catch (e) {}
      }
    }

    if (options.important) {
      try {
        await this.server.owner.send(`An important notice was triggered in your server "${this.server.name}" and there is no announcement channel configured, so it has been sent to you here:`, { embed })
        return
      } catch (e) {}

      if (this.server.systemChannel) {
        try {
          await this.server.systemChannel.send(`${this.server.owner} An important notice was triggered and there is no announcement channel configured and the server owner will not accept DMs from RoVer, so it has been posted here:`, { embed })
          return
        } catch (e) {}
      }

      if (options.lastResortChannel) {
        try {
          await options.lastResortChannel.send(`${this.server.owner} An important notice was triggered and there is no announcement channel configured and the server owner will not accept DMs from RoVer, so it has been posted here as a last resort:`, { embed })
        } catch (e) {}
      }
    }
  }

  /**
   * Gets this server's specific welcome message, or the default
   * one if none is configured.
   * @param {object} data String replacement data
   * @returns {string} The processed string
   * @memberof DiscordServer
   */
  getWelcomeMessage (data, member) {
    return Util.formatDataString(this.getSetting('welcomeMessage'), data, member)
  }

  /**
   * Checks to see if this server has configured a custom welcome message.
   * @returns {boolean} True if this server has a custom welcome message
   * @memberof DiscordServer
   */
  hasCustomWelcomeMessage () {
    return DefaultSettings.welcomeMessage !== this.getSetting('welcomeMessage')
  }

  /**
   * Returns a DiscordMember belonging to this DiscordServer.
   * @param {Snowflake} id The id of the user
   * @returns {Promise<DiscordMember>} The new discord member
   * @memberof DiscordServer
   */
  async getMember (id) {
    return DiscordMember.new(this, id)
  }

  /**
   * Returns whether or not a role is in use by the verified, not verified, or binding roles
   *
   * @param {Snowflake} id The role id
   * @returns {boolean} True if the role is in use
   * @memberof DiscordServer
   */
  isRoleInUse (id) {
    if (this.getSetting('verifiedRole') === id) return true
    if (this.getSetting('verifiedRemovedRole') === id) return true

    for (let binding of this.getSetting('groupRankBindings')) {
      if (binding.role === id) return true
    }

    return false
  }
}

module.exports = DiscordServer
<|endoftext|>