<commit_before>/* calculator-beckend.js
 * This file contains all the transcript parsing and
 * and GPA calculation logic.
 */

 /* Input: object containing the raw text of the transcript.
  * Output: list of course objects, as interpreted by CBE rules. */
function parseClassesCBE(info) {
  //console.log("readFromPageCBE()");
  var classList = [];
  var localData = String(info.data);
  var lines = localData.split('\n');
  var headers = [
    'ECON',
    'ACCT',
    'DSCI',
    'MIS',
    'FIN',
    'MKTG',
    'OPS',
    'MGMT',
    'IBUS',
    'HRM'
  ];
  var grades = [
    'A',
    'A-',
    'KA',
    'KA-',
    'B',
    'B+',
    'B-',
    'K',
    'K*',
    'KB',
    'KB+',
    'KB-',
    'C',
    'C+',
    'C-',
    'KC',
    'KC+',
    'KC-',
    'D',
    'D+',
    'D-',
    'KD',
    'KD+',
    'KD-',
    'F',
    'KF',
    'S',
    'U',
    'KZ',
    'Z'
  ];
  for(var i = 0 ; i < lines.length ; i++){
    //split on space or group of spaces and store in lineArray
    var lineArray = lines[i].trim().split(/\s+/);
    // check if the line has a class listed in the class prefix list
    if(headers.indexOf(lineArray[0]) >= 0){
      var tempName = (lineArray[0] + ' ' + lineArray[1]).substring(0, 8)
      var tempGrade;
      var tempCredits;
      var realGrade = false;

      // start at gpa position for course record and loop to end of line
      for(var ind = 5; ind < lineArray.length; ind++){
        if(grades.indexOf(lineArray[ind])>=0){
          tempGrade = lineArray[ind];

          //Class has a '*' after the grade
          if(tempGrade[1] === '*' || tempGrade[2] === '*') {
            //tempGrade = tempGrade.substring(1,tempGrade.length);
            //console.log("K was hit, tempgrade=" + tempGrade);
            break;
          }

          //If class is pass/fail, break loop and ignore it
          if((tempGrade[0] === 'S') || (tempGrade[0] === 'U')){
            break;
          }

          //credits are located one before the grade.
          tempCredits = lineArray[ind-1];
          realGrade = true;
          break;
        }
      }

      if(realGrade){
        for(var j = 0 ; j < classList.length ; j++){
          if(classList[j].name === tempName){
            classList[j].composite = 'unique';
          }
        }

        classList.push({
          name: tempName,
          grade: tempGrade,
          gpa: getGPAValue(tempGrade).toFixed(1),
          credits: tempCredits
        });
      }
    }
  }

  return classList;
}

 /* Input: object containing the raw text of the transcript.
  * Output: list of course objects, as interpreted by MSCM rules. */
function parseClassesMSCM(info) {
  //console.debug("readFromPageMSCM()");
  var classList = [];
  var localData = String(info.data);
  var lines = localData.split('\n');
  var validclasses = {
    'MATH': ['157'],
    'DSCI': ['205'],
    'ACCT': ['240','245'],
    'ECON':['206','207'],
    'MIS': ['220'],
    'MGMT':['271'],
    'PHYS':['114'],
    'CHEM': ['121']
  }
  var grades = [
    'A',
    'A-',
    'K',
    'K*',
    'KA',
    'KA-',
    'B',
    'B+',
    'B-',
    'KB',
    'KB+',
    'KB-',
    'C',
    'C+',
    'C-',
    'KC',
    'KC+',
    'KC-',
    'D',
    'D+',
    'D-',
    'KD',
    'KD+',
    'KD-',
    'F',
    'KF',
    'S',
    'U',
    'KZ',
    'Z'
  ];
  for(var i = 0 ; i < lines.length ; i++){
    //split on space or group of spaces and store in lineArray
    var lineArray = lines[i].trim().split(/\s+/);

    if(validclasses.hasOwnProperty(lineArray[0]) && validclasses[lineArray[0]].indexOf(lineArray[1].substring(0,3))>=0){
      var tempName = (lineArray[0] + ' ' + lineArray[1]).substring(0, 8)
      var tempGrade;
      var tempCredits;

      var realGrade = false;
      for(var ind = 5; ind < lineArray.length; ind++){
        if(grades.indexOf(lineArray[ind])>=0){
          tempGrade = lineArray[ind];

          //Class has a 'K' preceeding the grade
          if(tempGrade[1] === '*' || tempGrade[2] === '*') {
            break;
          }

          //If class is pass/fail, break loop and ignore it
          if((tempGrade[0] === 'S') || (tempGrade[0] === 'U')){
            break;
          }

          //credits are located one before the grade.
          tempCredits = lineArray[ind-1];
          realGrade = true;
          break;
        }
      }

      if(realGrade){
        for(var j = 0 ; j < classList.length ; j++){
          if(classList[j].name === tempName){
            /* Remove duplicates */
            classList.splice(j, 1);
            j--;

          }
        }

        classList.push({
          name: tempName,
          grade: tempGrade,
          gpa: getGPAValue(tempGrade).toFixed(1),
          credits: tempCredits
        });
      }
    }
  }

  return classList;
}

/* Input: list of course objects
 * Output: GPA and total credits for MSCM, as well as
 *         the original list but modified to mark duplicates */
function calculateMSCMGPA(classList) {

  /* detect duplicates */
  for(var i = 0 ; i < classList.length ; i++){ //Remove unecessary "composite" flags

    /* don't touch K grade classes */
    if (classList[i].grade === "K" || classList[i].grade === "K*") {
      continue;
    }
    classList[i].composite = "unique";
    for(var j = i+1 ; j < classList.length ; j++){
      if(classList[j].name === classList[i].name){
        classList[i].composite = "composite";
      }
    }
  }

  var gpa = 0.00;
  var credits = 0.00;

  // tally gpa and credits for all classes in classList
  for(var i = 0 ; i < classList.length ; i++){
    if(classList[i].composite === "unique" && classList[i].gpa >= 0){
      gpa += (+classList[i].gpa * +classList[i].credits);
      credits +=  +classList[i].credits;
    }
  }

  if(gpa != 0){
    gpa = gpa / credits;
  }

  var gradeInfo = {
    gpa: gpa.toFixed(2),
    credits: credits,
    classList: classList
  };

  return gradeInfo;
}

/* Input: list of course objects
 * Output: GPA and total credits for CBE, as well as
 *         the original list but modified to mark duplicates */
function calculateCBEGPA(classList) {
  var gpa = 0.00;
  var credits = 0.00;

  //The number of credits minus credits from classes that were retaken
  for(var i = 0 ; i < classList.length ; i++){

    // only calculate info from standard gpa grades
    if(classList[i].gpa >= 0){
      gpa += (+classList[i].gpa * +classList[i].credits);
      credits += +classList[i].credits;
    }
  }

  if(gpa != 0){
    gpa = gpa / credits;
  }

  var gradeInfo = {
    gpa: gpa.toFixed(2),
    credits: credits,
    classList: classList
  };

  return gradeInfo;
}

/* Input: string representing letter grade
 * Output: floating point equivalent; -1 in cases where
 *         the grade doesn't factor in to the GPA. */
function getGPAValue(string){
  /* checks if it's a completed K grade */
  var re = /K[A-Z][\-+\++]?/;
  if (re.test(string)) {
    string = string.substring(1,4);
  }

  //console.log("getGPAValue()");
  var gpa;
  var letter = string.substring(0,1);
  if(string.length >= 2){

    var mod = string.substring(1,2);
    if(!(mod === '+' || mod ==='-')){
      var mod = '';
    }
  }

  if(letter === "a" || letter === 'A'){
    gpa = 4;
  }else if(letter === "b" || letter === 'B'){
    gpa = 3;
  }else if(letter === "c" || letter === 'C'){
    gpa = 2;
  }else if(letter === "d" || letter === 'D'){
    gpa = 1;
  }else if(letter === "f" || letter === 'F'){
    gpa = 0;
  }else if(letter === "z" || letter === 'Z'){
    gpa = 0;
  }else if(letter === "K" || string === 'K*'){
    gpa = -1;
  }else{
    return;
  }

  if(mod === '+' && gpa < 4){
    gpa += 0.3;
  }else if(mod === '-'){
    gpa -= 0.3;
  }

  return gpa;
}
<commit_msg>greyed out CBE dupes. Again.<commit_after>/* calculator-beckend.js
 * This file contains all the transcript parsing and
 * and GPA calculation logic.
 */

 /* Input: object containing the raw text of the transcript.
  * Output: list of course objects, as interpreted by CBE rules. */
function parseClassesCBE(info) {
  //console.log("readFromPageCBE()");
  var classList = [];
  var localData = String(info.data);
  var lines = localData.split('\n');
  var headers = [
    'ECON',
    'ACCT',
    'DSCI',
    'MIS',
    'FIN',
    'MKTG',
    'OPS',
    'MGMT',
    'IBUS',
    'HRM'
  ];
  var grades = [
    'A',
    'A-',
    'KA',
    'KA-',
    'B',
    'B+',
    'B-',
    'K',
    'K*',
    'KB',
    'KB+',
    'KB-',
    'C',
    'C+',
    'C-',
    'KC',
    'KC+',
    'KC-',
    'D',
    'D+',
    'D-',
    'KD',
    'KD+',
    'KD-',
    'F',
    'KF',
    'S',
    'U',
    'KZ',
    'Z'
  ];
  for(var i = 0 ; i < lines.length ; i++){
    //split on space or group of spaces and store in lineArray
    var lineArray = lines[i].trim().split(/\s+/);
    // check if the line has a class listed in the class prefix list
    if(headers.indexOf(lineArray[0]) >= 0){
      var tempName = (lineArray[0] + ' ' + lineArray[1]).substring(0, 8)
      var tempGrade;
      var tempCredits;
      var realGrade = false;

      // start at gpa position for course record and loop to end of line
      for(var ind = 5; ind < lineArray.length; ind++){
        if(grades.indexOf(lineArray[ind])>=0){
          tempGrade = lineArray[ind];

          //Class has a '*' after the grade
          if(tempGrade[1] === '*' || tempGrade[2] === '*') {
            //tempGrade = tempGrade.substring(1,tempGrade.length);
            //console.log("K was hit, tempgrade=" + tempGrade);
            break;
          }

          //If class is pass/fail, break loop and ignore it
          if((tempGrade[0] === 'S') || (tempGrade[0] === 'U')){
            break;
          }

          //credits are located one before the grade.
          tempCredits = lineArray[ind-1];
          realGrade = true;
          break;
        }
      }

      if(realGrade){
        for(var j = 0 ; j < classList.length ; j++){
          if(classList[j].name === tempName){
            classList[j].composite = 'composite';
          }
        }

        classList.push({
          name: tempName,
          grade: tempGrade,
          gpa: getGPAValue(tempGrade).toFixed(1),
          credits: tempCredits
        });
      }
    }
  }

  return classList;
}

 /* Input: object containing the raw text of the transcript.
  * Output: list of course objects, as interpreted by MSCM rules. */
function parseClassesMSCM(info) {
  //console.debug("readFromPageMSCM()");
  var classList = [];
  var localData = String(info.data);
  var lines = localData.split('\n');
  var validclasses = {
    'MATH': ['157'],
    'DSCI': ['205'],
    'ACCT': ['240','245'],
    'ECON':['206','207'],
    'MIS': ['220'],
    'MGMT':['271'],
    'PHYS':['114'],
    'CHEM': ['121']
  }
  var grades = [
    'A',
    'A-',
    'K',
    'K*',
    'KA',
    'KA-',
    'B',
    'B+',
    'B-',
    'KB',
    'KB+',
    'KB-',
    'C',
    'C+',
    'C-',
    'KC',
    'KC+',
    'KC-',
    'D',
    'D+',
    'D-',
    'KD',
    'KD+',
    'KD-',
    'F',
    'KF',
    'S',
    'U',
    'KZ',
    'Z'
  ];
  for(var i = 0 ; i < lines.length ; i++){
    //split on space or group of spaces and store in lineArray
    var lineArray = lines[i].trim().split(/\s+/);

    if(validclasses.hasOwnProperty(lineArray[0]) && validclasses[lineArray[0]].indexOf(lineArray[1].substring(0,3))>=0){
      var tempName = (lineArray[0] + ' ' + lineArray[1]).substring(0, 8)
      var tempGrade;
      var tempCredits;

      var realGrade = false;
      for(var ind = 5; ind < lineArray.length; ind++){
        if(grades.indexOf(lineArray[ind])>=0){
          tempGrade = lineArray[ind];

          //Class has a 'K' preceeding the grade
          if(tempGrade[1] === '*' || tempGrade[2] === '*') {
            break;
          }

          //If class is pass/fail, break loop and ignore it
          if((tempGrade[0] === 'S') || (tempGrade[0] === 'U')){
            break;
          }

          //credits are located one before the grade.
          tempCredits = lineArray[ind-1];
          realGrade = true;
          break;
        }
      }

      if(realGrade){
        for(var j = 0 ; j < classList.length ; j++){
          if(classList[j].name === tempName){
            /* Remove duplicates */
            classList.splice(j, 1);
            j--;

          }
        }

        classList.push({
          name: tempName,
          grade: tempGrade,
          gpa: getGPAValue(tempGrade).toFixed(1),
          credits: tempCredits
        });
      }
    }
  }

  return classList;
}

/* Input: list of course objects
 * Output: GPA and total credits for MSCM, as well as
 *         the original list but modified to mark duplicates */
function calculateMSCMGPA(classList) {

  /* detect duplicates */
  for(var i = 0 ; i < classList.length ; i++){ //Remove unecessary "composite" flags

    /* don't touch K grade classes */
    if (classList[i].grade === "K" || classList[i].grade === "K*") {
      continue;
    }
    classList[i].composite = "unique";
    for(var j = i+1 ; j < classList.length ; j++){
      if(classList[j].name === classList[i].name){
        classList[i].composite = "composite";
      }
    }
  }

  var gpa = 0.00;
  var credits = 0.00;

  // tally gpa and credits for all classes in classList
  for(var i = 0 ; i < classList.length ; i++){
    if(classList[i].composite === "unique" && classList[i].gpa >= 0){
      gpa += (+classList[i].gpa * +classList[i].credits);
      credits +=  +classList[i].credits;
    }
  }

  if(gpa != 0){
    gpa = gpa / credits;
  }

  var gradeInfo = {
    gpa: gpa.toFixed(2),
    credits: credits,
    classList: classList
  };

  return gradeInfo;
}

/* Input: list of course objects
 * Output: GPA and total credits for CBE, as well as
 *         the original list but modified to mark duplicates */
function calculateCBEGPA(classList) {
  var gpa = 0.00;
  var credits = 0.00;

  //The number of credits minus credits from classes that were retaken
  for(var i = 0 ; i < classList.length ; i++){

    // only calculate info from standard gpa grades
    if(classList[i].gpa >= 0){
      gpa += (+classList[i].gpa * +classList[i].credits);
      credits += +classList[i].credits;
    }
  }

  if(gpa != 0){
    gpa = gpa / credits;
  }

  var gradeInfo = {
    gpa: gpa.toFixed(2),
    credits: credits,
    classList: classList
  };

  return gradeInfo;
}

/* Input: string representing letter grade
 * Output: floating point equivalent; -1 in cases where
 *         the grade doesn't factor in to the GPA. */
function getGPAValue(string){
  /* checks if it's a completed K grade */
  var re = /K[A-Z][\-+\++]?/;
  if (re.test(string)) {
    string = string.substring(1,4);
  }

  //console.log("getGPAValue()");
  var gpa;
  var letter = string.substring(0,1);
  if(string.length >= 2){

    var mod = string.substring(1,2);
    if(!(mod === '+' || mod ==='-')){
      var mod = '';
    }
  }

  if(letter === "a" || letter === 'A'){
    gpa = 4;
  }else if(letter === "b" || letter === 'B'){
    gpa = 3;
  }else if(letter === "c" || letter === 'C'){
    gpa = 2;
  }else if(letter === "d" || letter === 'D'){
    gpa = 1;
  }else if(letter === "f" || letter === 'F'){
    gpa = 0;
  }else if(letter === "z" || letter === 'Z'){
    gpa = 0;
  }else if(letter === "K" || string === 'K*'){
    gpa = -1;
  }else{
    return;
  }

  if(mod === '+' && gpa < 4){
    gpa += 0.3;
  }else if(mod === '-'){
    gpa -= 0.3;
  }

  return gpa;
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

goog.module('goog.editor.LinkTest');
goog.setTestOnly();

const Link = goog.require('goog.editor.Link');
const NodeType = goog.require('goog.dom.NodeType');
const Range = goog.require('goog.dom.Range');
const TagName = goog.require('goog.dom.TagName');
const dom = goog.require('goog.dom');
const testSuite = goog.require('goog.testing.testSuite');

let anchor;

testSuite({
  setUp() {
    anchor = dom.createDom(TagName.A);
    document.body.appendChild(anchor);
  },

  tearDown() {
    dom.removeNode(anchor);
  },

  testCreateNew() {
    const link = new Link(anchor, true);
    assertNotNull('Should have created object', link);
    assertTrue('Should be new', link.isNew());
    assertEquals('Should have correct anchor', anchor, link.getAnchor());
    assertEquals('Should be empty', '', link.getCurrentText());
  },

  testCreateNotNew() {
    const link = new Link(anchor, false);
    assertNotNull('Should have created object', link);
    assertFalse('Should not be new', link.isNew());
    assertEquals('Should have correct anchor', anchor, link.getAnchor());
    assertEquals('Should be empty', '', link.getCurrentText());
  },

  testCreateNewLinkFromText() {
    const url = 'http://www.google.com/';
    anchor.innerHTML = url;
    const link = Link.createNewLinkFromText(anchor);
    assertNotNull('Should have created object', link);
    assertEquals('Should have url in anchor', url, anchor.href);
  },

  testCreateNewLinkFromTextLeadingTrailingWhitespace() {
    const url = 'http://www.google.com/';
    const urlWithSpaces = ` ${url} `;
    anchor.innerHTML = urlWithSpaces;
    const urlWithSpacesUpdatedByBrowser = anchor.innerHTML;
    const link = Link.createNewLinkFromText(anchor);
    assertNotNull('Should have created object', link);
    assertEquals('Should have url in anchor', url, anchor.href);
    assertEquals(
        'The text should still have spaces', urlWithSpacesUpdatedByBrowser,
        link.getCurrentText());
  },

  testCreateNewLinkFromTextWithAnchor() {
    const url = 'https://www.google.com/';
    anchor.innerHTML = url;
    const link = Link.createNewLinkFromText(anchor, '_blank');
    assertNotNull('Should have created object', link);
    assertEquals('Should have url in anchor', url, anchor.href);
    assertEquals('Should have _blank target', '_blank', anchor.target);
  },

  testInitialize() {
    const link = Link.createNewLink(anchor, 'http://www.google.com');
    assertNotNull('Should have created object', link);
    assertTrue('Should be new', link.isNew());
    assertEquals('Should have correct anchor', anchor, link.getAnchor());
    assertEquals('Should be empty', '', link.getCurrentText());
  },

  testInitializeWithTarget() {
    const link = Link.createNewLink(anchor, 'http://www.google.com', '_blank');
    assertNotNull('Should have created object', link);
    assertTrue('Should be new', link.isNew());
    assertEquals('Should have correct anchor', anchor, link.getAnchor());
    assertEquals('Should be empty', '', link.getCurrentText());
    assertEquals('Should have _blank target', '_blank', anchor.target);
  },

  testSetText() {
    const link = Link.createNewLink(anchor, 'http://www.google.com', '_blank');
    assertEquals('Should be empty', '', link.getCurrentText());
    link.setTextAndUrl('Text', 'http://docs.google.com/');
    assertEquals(
        'Should point to http://docs.google.com/', 'http://docs.google.com/',
        anchor.href);
    assertEquals('Should have correct text', 'Text', link.getCurrentText());
  },

  testSetBoldText() {
    anchor.innerHTML = '';
    const link = Link.createNewLink(anchor, 'http://www.google.com', '_blank');
    assertEquals('Should be empty', '', link.getCurrentText());
    link.setTextAndUrl('Text', 'http://docs.google.com/');
    assertEquals(
        'Should point to http://docs.google.com/', 'http://docs.google.com/',
        anchor.href);
    assertEquals('Should have correct text', 'Text', link.getCurrentText());
    assertEquals(
        'Should still be bold', String(TagName.B), anchor.firstChild.tagName);
  },

  testLinkImgTag() {
    anchor.innerHTML = '';
    const link = Link.createNewLink(anchor, 'http://www.google.com', '_blank');
    assertEquals('Test getCurrentText', 'alt_txt', link.getCurrentText());
    link.setTextAndUrl('newText', 'http://docs.google.com/');
    assertEquals('Test getCurrentText', 'newText', link.getCurrentText());
    assertEquals(
        'Should point to http://docs.google.com/', 'http://docs.google.com/',
        anchor.href);

    assertEquals(
        'Should still have img tag', String(TagName.IMG),
        anchor.firstChild.tagName);

    assertEquals(
        'Alt should equal "newText"', 'newText',
        anchor.firstChild.getAttribute('alt'));
  },

  testLinkImgTagWithNoAlt() {
    anchor.innerHTML = '';
    const link = Link.createNewLink(anchor, 'http://www.google.com', '_blank');
    assertEquals('Test getCurrentText', '', link.getCurrentText());
  },

  testSetMixed() {
    anchor.innerHTML = 'AB';
    const link = Link.createNewLink(anchor, 'http://www.google.com', '_blank');
    assertEquals('Should have text: AB', 'AB', link.getCurrentText());
    link.setTextAndUrl('Text', 'http://docs.google.com/');
    assertEquals(
        'Should point to http://docs.google.com/', 'http://docs.google.com/',
        anchor.href);
    assertEquals('Should have correct text', 'Text', link.getCurrentText());
    assertEquals(
        'Should not be bold', NodeType.TEXT, anchor.firstChild.nodeType);
  },

  testPlaceCursorRightOf() {
    // IE can only do selections properly if the region is editable.
    const ed = dom.createDom(TagName.DIV);
    dom.replaceNode(ed, anchor);
    ed.contentEditable = true;
    ed.appendChild(anchor);

    // In order to test the cursor placement properly, we need to have
    // link text.  See more details in the test below.
    dom.setTextContent(anchor, 'I am text');

    const link = Link.createNewLink(anchor, 'http://www.google.com');
    link.placeCursorRightOf();

    const range = Range.createFromWindow();
    assertTrue('Range should be collapsed', range.isCollapsed());
    const startNode = range.getStartNode();

    // Check that the selection is the "right" place.
    //
    // If you query the selection, it is actually still inside the anchor,
    // but if you type, it types outside the anchor.
    //
    // Best we can do is test that it is at the end of the anchor text.
    assertEquals(
        'Selection should be in anchor text', anchor.firstChild, startNode);
    assertEquals(
        'Selection should be at the end of the text', anchor.firstChild.length,
        range.getStartOffset());

    if (ed) {
      dom.removeNode(ed);
    }
  },

  testIsLikelyUrl() {
    const good = [
      // Proper URLs
      'http://google.com',
      'http://google.com/',
      'http://192.168.1.103',
      'http://www.google.com:8083',
      'https://antoine',
      'https://foo.foo.net',
      'ftp://google.com:22/',
      'http://user@site.com',
      'ftp://user:pass@ftp.site.com',
      'http://google.com/search?q=laser%20cats',
      'aim:goim?screenname=en2es',
      'mailto:x@y.com',

      // Bad URLs a browser will accept
      'www.google.com',
      'www.amazon.co.uk',
      'amazon.co.uk',
      'foo2.foo3.com',
      'pandora.tv',
      'marketing.us',
      'del.icio.us',
      'bridge-line.com',
      'www.frigid.net:80',
      'www.google.com?q=foo',
      'www.foo.com/j%20.txt',
      'foodtv.net',
      'google.com',
      'slashdot.org',
      '192.168.1.1',
      'justin.edu?kumar something',
      'google.com/search?q=hot%20pockets',

      // Due to TLD explosion, these could be URLs either now or soon.
      'ww.jester',
      'juicer.fake',
      'abs.nonsense.something',
      'filename.txt',
    ];
    let i;
    for (i = 0; i < good.length; i++) {
      assertTrue(good[i] + ' should be good', Link.isLikelyUrl(good[i]));
    }

    const bad = [
      // Definitely not URLs
      'bananas',
      'http google com',
      '',
      'Sad :/',
      '*garbage!.123',
      'ftp',
      'http',
      '/',
      'https',
      'this is',
      '*!&.banana!*&!',
      'www.jester is gone.com',
      'ftp .nospaces.net',
      'www_foo_net',
      'www.\'jester\'.net',
      'www:8080',
      'www . notnsense.com',
      'email@address.com',
      '.',
      'x.',

      // URL-ish but not quite
      '  http://www.google.com',
      'http://www.google.com:8081   ',
      'www.google.com foo bar',
      'google.com/search?q=not quite',
    ];

    for (i = 0; i < bad.length; i++) {
      assertFalse(bad[i] + ' should be bad', Link.isLikelyUrl(bad[i]));
    }
  },

  testIsLikelyEmailAddress() {
    const good = [
      // Valid email addresses
      'foo@foo.com',
      'foo1@foo2.foo3.com',
      'f45_1@goog13.org',
      'user@gmail.co.uk',
      'jon-smith@crazy.net',
      'roland1@capuchino.gov',
      'ernir@gshi.nl',
      'JOON@jno.COM',
      'media@meDIa.fREnology.FR',
      'john.mail4me@del.icio.us',
      'www9@wc3.madeup1.org',
      'hi@192.168.1.103',
      'hi@192.168.1.1',
    ];
    let i;
    for (i = 0; i < good.length; i++) {
      assertTrue(Link.isLikelyEmailAddress(good[i]));
    }

    const bad = [
      // Malformed/incomplete email addresses
      'user',
      '@gmail.com',
      'user@gmail',
      'user@.com',
      'user@gmail.c',
      'user@gmail.co.u',
      '@ya.com',
      '.@hi3.nl',
      'jim.com',
      'ed:@gmail.com',
      '*!&.banana!*&!',
      ':jon@gmail.com',
      '3g?@bil.com',
      'adam be@hi.net',
      'john\nsmith@test.com',
      'www.\'jester\'.net',
      '\'james\'@covald.net',
      'ftp://user@site.com/',
      'aim:goim?screenname=en2es',
      'user:pass@site.com',
      'user@site.com yay',
    ];

    for (i = 0; i < bad.length; i++) {
      assertFalse(Link.isLikelyEmailAddress(bad[i]));
    }
  },

  /** @suppress {checkTypes} suppression added to enable type checking */
  testIsMailToLink() {
    assertFalse(Link.isMailto());
    assertFalse(Link.isMailto(null));
    assertFalse(Link.isMailto(''));
    assertFalse(Link.isMailto('http://foo.com'));
    assertFalse(Link.isMailto('http://mailto:80'));

    assertTrue(Link.isMailto('mailto:'));
    assertTrue(Link.isMailto('mailto://'));
    assertTrue(Link.isMailto('mailto://ptucker@gmail.com'));
  },

  testGetValidLinkFromText() {
    const textLinkPairs = [
      // input text, expected link output
      'www.foo.com',
      'http://www.foo.com',
      'user@gmail.com',
      'mailto:user@gmail.com',
      'http://www.foo.com',
      'http://www.foo.com',
      'https://this.that.edu',
      'https://this.that.edu',
      'nothing to see here',
      null,
    ];
    const link = new Link(anchor, true);

    for (let i = 0; i < textLinkPairs.length; i += 2) {
      /**
       * @suppress {strictMissingProperties} suppression added to enable type
       * checking
       */
      link.currentText_ = textLinkPairs[i];
      const result = link.getValidLinkFromText();
      assertEquals(textLinkPairs[i + 1], result);
    }
  },
});
<commit_msg>Scrub a personal email address from test data. RELNOTES: N/A<commit_after>/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

goog.module('goog.editor.LinkTest');
goog.setTestOnly();

const Link = goog.require('goog.editor.Link');
const NodeType = goog.require('goog.dom.NodeType');
const Range = goog.require('goog.dom.Range');
const TagName = goog.require('goog.dom.TagName');
const dom = goog.require('goog.dom');
const testSuite = goog.require('goog.testing.testSuite');

let anchor;

testSuite({
  setUp() {
    anchor = dom.createDom(TagName.A);
    document.body.appendChild(anchor);
  },

  tearDown() {
    dom.removeNode(anchor);
  },

  testCreateNew() {
    const link = new Link(anchor, true);
    assertNotNull('Should have created object', link);
    assertTrue('Should be new', link.isNew());
    assertEquals('Should have correct anchor', anchor, link.getAnchor());
    assertEquals('Should be empty', '', link.getCurrentText());
  },

  testCreateNotNew() {
    const link = new Link(anchor, false);
    assertNotNull('Should have created object', link);
    assertFalse('Should not be new', link.isNew());
    assertEquals('Should have correct anchor', anchor, link.getAnchor());
    assertEquals('Should be empty', '', link.getCurrentText());
  },

  testCreateNewLinkFromText() {
    const url = 'http://www.google.com/';
    anchor.innerHTML = url;
    const link = Link.createNewLinkFromText(anchor);
    assertNotNull('Should have created object', link);
    assertEquals('Should have url in anchor', url, anchor.href);
  },

  testCreateNewLinkFromTextLeadingTrailingWhitespace() {
    const url = 'http://www.google.com/';
    const urlWithSpaces = ` ${url} `;
    anchor.innerHTML = urlWithSpaces;
    const urlWithSpacesUpdatedByBrowser = anchor.innerHTML;
    const link = Link.createNewLinkFromText(anchor);
    assertNotNull('Should have created object', link);
    assertEquals('Should have url in anchor', url, anchor.href);
    assertEquals(
        'The text should still have spaces', urlWithSpacesUpdatedByBrowser,
        link.getCurrentText());
  },

  testCreateNewLinkFromTextWithAnchor() {
    const url = 'https://www.google.com/';
    anchor.innerHTML = url;
    const link = Link.createNewLinkFromText(anchor, '_blank');
    assertNotNull('Should have created object', link);
    assertEquals('Should have url in anchor', url, anchor.href);
    assertEquals('Should have _blank target', '_blank', anchor.target);
  },

  testInitialize() {
    const link = Link.createNewLink(anchor, 'http://www.google.com');
    assertNotNull('Should have created object', link);
    assertTrue('Should be new', link.isNew());
    assertEquals('Should have correct anchor', anchor, link.getAnchor());
    assertEquals('Should be empty', '', link.getCurrentText());
  },

  testInitializeWithTarget() {
    const link = Link.createNewLink(anchor, 'http://www.google.com', '_blank');
    assertNotNull('Should have created object', link);
    assertTrue('Should be new', link.isNew());
    assertEquals('Should have correct anchor', anchor, link.getAnchor());
    assertEquals('Should be empty', '', link.getCurrentText());
    assertEquals('Should have _blank target', '_blank', anchor.target);
  },

  testSetText() {
    const link = Link.createNewLink(anchor, 'http://www.google.com', '_blank');
    assertEquals('Should be empty', '', link.getCurrentText());
    link.setTextAndUrl('Text', 'http://docs.google.com/');
    assertEquals(
        'Should point to http://docs.google.com/', 'http://docs.google.com/',
        anchor.href);
    assertEquals('Should have correct text', 'Text', link.getCurrentText());
  },

  testSetBoldText() {
    anchor.innerHTML = '';
    const link = Link.createNewLink(anchor, 'http://www.google.com', '_blank');
    assertEquals('Should be empty', '', link.getCurrentText());
    link.setTextAndUrl('Text', 'http://docs.google.com/');
    assertEquals(
        'Should point to http://docs.google.com/', 'http://docs.google.com/',
        anchor.href);
    assertEquals('Should have correct text', 'Text', link.getCurrentText());
    assertEquals(
        'Should still be bold', String(TagName.B), anchor.firstChild.tagName);
  },

  testLinkImgTag() {
    anchor.innerHTML = '';
    const link = Link.createNewLink(anchor, 'http://www.google.com', '_blank');
    assertEquals('Test getCurrentText', 'alt_txt', link.getCurrentText());
    link.setTextAndUrl('newText', 'http://docs.google.com/');
    assertEquals('Test getCurrentText', 'newText', link.getCurrentText());
    assertEquals(
        'Should point to http://docs.google.com/', 'http://docs.google.com/',
        anchor.href);

    assertEquals(
        'Should still have img tag', String(TagName.IMG),
        anchor.firstChild.tagName);

    assertEquals(
        'Alt should equal "newText"', 'newText',
        anchor.firstChild.getAttribute('alt'));
  },

  testLinkImgTagWithNoAlt() {
    anchor.innerHTML = '';
    const link = Link.createNewLink(anchor, 'http://www.google.com', '_blank');
    assertEquals('Test getCurrentText', '', link.getCurrentText());
  },

  testSetMixed() {
    anchor.innerHTML = 'AB';
    const link = Link.createNewLink(anchor, 'http://www.google.com', '_blank');
    assertEquals('Should have text: AB', 'AB', link.getCurrentText());
    link.setTextAndUrl('Text', 'http://docs.google.com/');
    assertEquals(
        'Should point to http://docs.google.com/', 'http://docs.google.com/',
        anchor.href);
    assertEquals('Should have correct text', 'Text', link.getCurrentText());
    assertEquals(
        'Should not be bold', NodeType.TEXT, anchor.firstChild.nodeType);
  },

  testPlaceCursorRightOf() {
    // IE can only do selections properly if the region is editable.
    const ed = dom.createDom(TagName.DIV);
    dom.replaceNode(ed, anchor);
    ed.contentEditable = true;
    ed.appendChild(anchor);

    // In order to test the cursor placement properly, we need to have
    // link text.  See more details in the test below.
    dom.setTextContent(anchor, 'I am text');

    const link = Link.createNewLink(anchor, 'http://www.google.com');
    link.placeCursorRightOf();

    const range = Range.createFromWindow();
    assertTrue('Range should be collapsed', range.isCollapsed());
    const startNode = range.getStartNode();

    // Check that the selection is the "right" place.
    //
    // If you query the selection, it is actually still inside the anchor,
    // but if you type, it types outside the anchor.
    //
    // Best we can do is test that it is at the end of the anchor text.
    assertEquals(
        'Selection should be in anchor text', anchor.firstChild, startNode);
    assertEquals(
        'Selection should be at the end of the text', anchor.firstChild.length,
        range.getStartOffset());

    if (ed) {
      dom.removeNode(ed);
    }
  },

  testIsLikelyUrl() {
    const good = [
      // Proper URLs
      'http://google.com',
      'http://google.com/',
      'http://192.168.1.103',
      'http://www.google.com:8083',
      'https://antoine',
      'https://foo.foo.net',
      'ftp://google.com:22/',
      'http://user@site.com',
      'ftp://user:pass@ftp.site.com',
      'http://google.com/search?q=laser%20cats',
      'aim:goim?screenname=en2es',
      'mailto:x@y.com',

      // Bad URLs a browser will accept
      'www.google.com',
      'www.amazon.co.uk',
      'amazon.co.uk',
      'foo2.foo3.com',
      'pandora.tv',
      'marketing.us',
      'del.icio.us',
      'bridge-line.com',
      'www.frigid.net:80',
      'www.google.com?q=foo',
      'www.foo.com/j%20.txt',
      'foodtv.net',
      'google.com',
      'slashdot.org',
      '192.168.1.1',
      'justin.edu?kumar something',
      'google.com/search?q=hot%20pockets',

      // Due to TLD explosion, these could be URLs either now or soon.
      'ww.jester',
      'juicer.fake',
      'abs.nonsense.something',
      'filename.txt',
    ];
    let i;
    for (i = 0; i < good.length; i++) {
      assertTrue(good[i] + ' should be good', Link.isLikelyUrl(good[i]));
    }

    const bad = [
      // Definitely not URLs
      'bananas',
      'http google com',
      '',
      'Sad :/',
      '*garbage!.123',
      'ftp',
      'http',
      '/',
      'https',
      'this is',
      '*!&.banana!*&!',
      'www.jester is gone.com',
      'ftp .nospaces.net',
      'www_foo_net',
      'www.\'jester\'.net',
      'www:8080',
      'www . notnsense.com',
      'email@address.com',
      '.',
      'x.',

      // URL-ish but not quite
      '  http://www.google.com',
      'http://www.google.com:8081   ',
      'www.google.com foo bar',
      'google.com/search?q=not quite',
    ];

    for (i = 0; i < bad.length; i++) {
      assertFalse(bad[i] + ' should be bad', Link.isLikelyUrl(bad[i]));
    }
  },

  testIsLikelyEmailAddress() {
    const good = [
      // Valid email addresses
      'foo@foo.com',
      'foo1@foo2.foo3.com',
      'f45_1@goog13.org',
      'user@gmail.co.uk',
      'jon-smith@crazy.net',
      'roland1@capuchino.gov',
      'ernir@gshi.nl',
      'JOON@jno.COM',
      'media@meDIa.fREnology.FR',
      'john.mail4me@del.icio.us',
      'www9@wc3.madeup1.org',
      'hi@192.168.1.103',
      'hi@192.168.1.1',
    ];
    let i;
    for (i = 0; i < good.length; i++) {
      assertTrue(Link.isLikelyEmailAddress(good[i]));
    }

    const bad = [
      // Malformed/incomplete email addresses
      'user',
      '@gmail.com',
      'user@gmail',
      'user@.com',
      'user@gmail.c',
      'user@gmail.co.u',
      '@ya.com',
      '.@hi3.nl',
      'jim.com',
      'ed:@gmail.com',
      '*!&.banana!*&!',
      ':jon@gmail.com',
      '3g?@bil.com',
      'adam be@hi.net',
      'john\nsmith@test.com',
      'www.\'jester\'.net',
      '\'james\'@covald.net',
      'ftp://user@site.com/',
      'aim:goim?screenname=en2es',
      'user:pass@site.com',
      'user@site.com yay',
    ];

    for (i = 0; i < bad.length; i++) {
      assertFalse(Link.isLikelyEmailAddress(bad[i]));
    }
  },

  /** @suppress {checkTypes} suppression added to enable type checking */
  testIsMailToLink() {
    assertFalse(Link.isMailto());
    assertFalse(Link.isMailto(null));
    assertFalse(Link.isMailto(''));
    assertFalse(Link.isMailto('http://foo.com'));
    assertFalse(Link.isMailto('http://mailto:80'));

    assertTrue(Link.isMailto('mailto:'));
    assertTrue(Link.isMailto('mailto://'));
    assertTrue(Link.isMailto('mailto://foo@gmail.com'));
  },

  testGetValidLinkFromText() {
    const textLinkPairs = [
      // input text, expected link output
      'www.foo.com',
      'http://www.foo.com',
      'user@gmail.com',
      'mailto:user@gmail.com',
      'http://www.foo.com',
      'http://www.foo.com',
      'https://this.that.edu',
      'https://this.that.edu',
      'nothing to see here',
      null,
    ];
    const link = new Link(anchor, true);

    for (let i = 0; i < textLinkPairs.length; i += 2) {
      /**
       * @suppress {strictMissingProperties} suppression added to enable type
       * checking
       */
      link.currentText_ = textLinkPairs[i];
      const result = link.getValidLinkFromText();
      assertEquals(textLinkPairs[i + 1], result);
    }
  },
});
<|endoftext|>


<commit_before>const Eris = require('eris');

const config = require('./config');
const bot = require('./bot');
const Queue = require('./queue');
const utils = require('./utils');
const blocked = require('./data/blocked');
const threads = require('./data/threads');

const reply = require('./modules/reply');
const close = require('./modules/close');
const snippets = require('./modules/snippets');
const logs = require('./modules/logs');
const move = require('./modules/move');
const block = require('./modules/block');
const suspend = require('./modules/suspend');
const webserver = require('./modules/webserver');
const greeting = require('./modules/greeting');
const typingProxy = require('./modules/typingProxy');
const version = require('./modules/version');
const newthread = require('./modules/newthread');
const idModule = require('./modules/id');
const alert = require('./modules/alert');

const attachments = require("./data/attachments");
const {ACCIDENTAL_THREAD_MESSAGES} = require('./data/constants');

const messageQueue = new Queue();

// Once the bot has connected, set the status/"playing" message
bot.on('ready', () => {
  bot.editStatus(null, {name: config.status});
  console.log('Connected! Now listening to DMs.');
});

/**
 * When a moderator posts in a modmail thread...
 * 1) If alwaysReply is enabled, reply to the user
 * 2) If alwaysReply is disabled, save that message as a chat message in the thread
 */
bot.on('messageCreate', async msg => {
  if (! utils.messageIsOnInboxServer(msg)) return;
  if (msg.author.bot) return;

  const thread = await threads.findByChannelId(msg.channel.id);
  if (! thread) return;

  if (msg.content.startsWith(config.prefix) || msg.content.startsWith(config.snippetPrefix)) {
    // Save commands as "command messages"
    if (msg.content.startsWith(config.snippetPrefix)) return; // Ignore snippets
    thread.saveCommandMessage(msg);
  } else if (config.alwaysReply) {
    // AUTO-REPLY: If config.alwaysReply is enabled, send all chat messages in thread channels as replies
    if (! utils.isStaff(msg.member)) return; // Only staff are allowed to reply

    if (msg.attachments.length) await attachments.saveAttachmentsInMessage(msg);
    await thread.replyToUser(msg.member, msg.content.trim(), msg.attachments, config.alwaysReplyAnon || false);
    msg.delete();
  } else {
    // Otherwise just save the messages as "chat" in the logs
    thread.saveChatMessage(msg);
  }
});

/**
 * When we get a private message...
 * 1) Find the open modmail thread for this user, or create a new one
 * 2) Post the message as a user reply in the thread
 */
bot.on('messageCreate', async msg => {
  if (! (msg.channel instanceof Eris.PrivateChannel)) return;
  if (msg.author.bot) return;
  if (msg.type !== 0) return; // Ignore pins etc.

  if (await blocked.isBlocked(msg.author.id)) return;

  // Private message handling is queued so e.g. multiple message in quick succession don't result in multiple channels being created
  messageQueue.add(async () => {
    let thread = await threads.findOpenThreadByUserId(msg.author.id);

    // New thread
    if (! thread) {
      // Ignore messages that shouldn't usually open new threads, such as "ok", "thanks", etc.
      if (config.ignoreAccidentalThreads && msg.content && ACCIDENTAL_THREAD_MESSAGES.includes(msg.content.trim().toLowerCase())) return;

      thread = await threads.createNewThreadForUser(msg.author);
    }

    if (thread) await thread.receiveUserReply(msg);
  });
});

/**
 * When a message is edited...
 * 1) If that message was in DMs, and we have a thread open with that user, post the edit as a system message in the thread
 * 2) If that message was moderator chatter in the thread, update the corresponding chat message in the DB
 */
bot.on('messageUpdate', async (msg, oldMessage) => {
  if (! msg || ! msg.author) return;
  if (msg.author.bot) return;
  if (await blocked.isBlocked(msg.author.id)) return;

  // Old message content doesn't persist between bot restarts
  const oldContent = oldMessage && oldMessage.content || '*Unavailable due to bot restart*';
  const newContent = msg.content;

  // Ignore bogus edit events with no changes
  if (newContent.trim() === oldContent.trim()) return;

  // 1) Edit in DMs
  if (msg.channel instanceof Eris.PrivateChannel) {
    const thread = await threads.findOpenThreadByUserId(msg.author.id);
    if (! thread) return;

    const editMessage = utils.disableLinkPreviews(`**The user edited their message:**\n\`B:\` ${oldContent}\n\`A:\` ${newContent}`);
    thread.postSystemMessage(editMessage);
  }

  // 2) Edit in the thread
  else if (utils.messageIsOnInboxServer(msg) && utils.isStaff(msg.member)) {
    const thread = await threads.findOpenThreadByChannelId(msg.channel.id);
    if (! thread) return;

    thread.updateChatMessage(msg);
  }
});

/**
 * When a staff message is deleted in a modmail thread, delete it from the database as well
 */
bot.on('messageDelete', async msg => {
  if (! msg.author) return;
  if (msg.author.bot) return;
  if (! utils.messageIsOnInboxServer(msg)) return;
  if (! utils.isStaff(msg.member)) return;

  const thread = await threads.findOpenThreadByChannelId(msg.channel.id);
  if (! thread) return;

  thread.deleteChatMessage(msg.id);
});

/**
 * When the bot is mentioned on the main server, ping staff in the log channel about it
 */
bot.on('messageCreate', async msg => {
  if (! utils.messageIsOnMainServer(msg)) return;
  if (! msg.mentions.some(user => user.id === bot.user.id)) return;
  if (msg.author.bot) return;

  // If the person who mentioned the modmail bot is also on the modmail server, ignore them
  if (utils.getInboxGuild().members.get(msg.author.id)) return;

  // If the person who mentioned the bot is blocked, ignore them
  if (await blocked.isBlocked(msg.author.id)) return;

  let content;
  const mainGuilds = utils.getMainGuilds();
  const staffMention = (config.pingOnBotMention ? utils.getInboxMention() : '');

  if (mainGuilds.length === 1) {
    content = `${staffMention}Bot mentioned in ${msg.channel.mention} by **${msg.author.username}#${msg.author.discriminator}**: "${msg.cleanContent}"`;
  } else {
    content = `${staffMention}Bot mentioned in ${msg.channel.mention} (${msg.channel.guild.name}) by **${msg.author.username}#${msg.author.discriminator}**: "${msg.cleanContent}"`;
  }

  bot.createMessage(utils.getLogChannel(bot).id, {
    content,
    disableEveryone: false,
  });
});

module.exports = {
  async start() {
    // Load modules
    console.log('Loading modules...');
    await reply(bot);
    await close(bot);
    await logs(bot);
    await block(bot);
    await move(bot);
    await snippets(bot);
    await suspend(bot);
    await greeting(bot);
    await webserver(bot);
    await typingProxy(bot);
    await version(bot);
    await newthread(bot);
    await idModule(bot);
    await alert(bot);

    // Connect to Discord
    console.log('Connecting to Discord...');
    await bot.connect();
  }
};
<commit_msg>Fix pingOnBotMention on same server setups. Fixes #134<commit_after>const Eris = require('eris');

const config = require('./config');
const bot = require('./bot');
const Queue = require('./queue');
const utils = require('./utils');
const blocked = require('./data/blocked');
const threads = require('./data/threads');

const reply = require('./modules/reply');
const close = require('./modules/close');
const snippets = require('./modules/snippets');
const logs = require('./modules/logs');
const move = require('./modules/move');
const block = require('./modules/block');
const suspend = require('./modules/suspend');
const webserver = require('./modules/webserver');
const greeting = require('./modules/greeting');
const typingProxy = require('./modules/typingProxy');
const version = require('./modules/version');
const newthread = require('./modules/newthread');
const idModule = require('./modules/id');
const alert = require('./modules/alert');

const attachments = require("./data/attachments");
const {ACCIDENTAL_THREAD_MESSAGES} = require('./data/constants');

const messageQueue = new Queue();

// Once the bot has connected, set the status/"playing" message
bot.on('ready', () => {
  bot.editStatus(null, {name: config.status});
  console.log('Connected! Now listening to DMs.');
});

/**
 * When a moderator posts in a modmail thread...
 * 1) If alwaysReply is enabled, reply to the user
 * 2) If alwaysReply is disabled, save that message as a chat message in the thread
 */
bot.on('messageCreate', async msg => {
  if (! utils.messageIsOnInboxServer(msg)) return;
  if (msg.author.bot) return;

  const thread = await threads.findByChannelId(msg.channel.id);
  if (! thread) return;

  if (msg.content.startsWith(config.prefix) || msg.content.startsWith(config.snippetPrefix)) {
    // Save commands as "command messages"
    if (msg.content.startsWith(config.snippetPrefix)) return; // Ignore snippets
    thread.saveCommandMessage(msg);
  } else if (config.alwaysReply) {
    // AUTO-REPLY: If config.alwaysReply is enabled, send all chat messages in thread channels as replies
    if (! utils.isStaff(msg.member)) return; // Only staff are allowed to reply

    if (msg.attachments.length) await attachments.saveAttachmentsInMessage(msg);
    await thread.replyToUser(msg.member, msg.content.trim(), msg.attachments, config.alwaysReplyAnon || false);
    msg.delete();
  } else {
    // Otherwise just save the messages as "chat" in the logs
    thread.saveChatMessage(msg);
  }
});

/**
 * When we get a private message...
 * 1) Find the open modmail thread for this user, or create a new one
 * 2) Post the message as a user reply in the thread
 */
bot.on('messageCreate', async msg => {
  if (! (msg.channel instanceof Eris.PrivateChannel)) return;
  if (msg.author.bot) return;
  if (msg.type !== 0) return; // Ignore pins etc.

  if (await blocked.isBlocked(msg.author.id)) return;

  // Private message handling is queued so e.g. multiple message in quick succession don't result in multiple channels being created
  messageQueue.add(async () => {
    let thread = await threads.findOpenThreadByUserId(msg.author.id);

    // New thread
    if (! thread) {
      // Ignore messages that shouldn't usually open new threads, such as "ok", "thanks", etc.
      if (config.ignoreAccidentalThreads && msg.content && ACCIDENTAL_THREAD_MESSAGES.includes(msg.content.trim().toLowerCase())) return;

      thread = await threads.createNewThreadForUser(msg.author);
    }

    if (thread) await thread.receiveUserReply(msg);
  });
});

/**
 * When a message is edited...
 * 1) If that message was in DMs, and we have a thread open with that user, post the edit as a system message in the thread
 * 2) If that message was moderator chatter in the thread, update the corresponding chat message in the DB
 */
bot.on('messageUpdate', async (msg, oldMessage) => {
  if (! msg || ! msg.author) return;
  if (msg.author.bot) return;
  if (await blocked.isBlocked(msg.author.id)) return;

  // Old message content doesn't persist between bot restarts
  const oldContent = oldMessage && oldMessage.content || '*Unavailable due to bot restart*';
  const newContent = msg.content;

  // Ignore bogus edit events with no changes
  if (newContent.trim() === oldContent.trim()) return;

  // 1) Edit in DMs
  if (msg.channel instanceof Eris.PrivateChannel) {
    const thread = await threads.findOpenThreadByUserId(msg.author.id);
    if (! thread) return;

    const editMessage = utils.disableLinkPreviews(`**The user edited their message:**\n\`B:\` ${oldContent}\n\`A:\` ${newContent}`);
    thread.postSystemMessage(editMessage);
  }

  // 2) Edit in the thread
  else if (utils.messageIsOnInboxServer(msg) && utils.isStaff(msg.member)) {
    const thread = await threads.findOpenThreadByChannelId(msg.channel.id);
    if (! thread) return;

    thread.updateChatMessage(msg);
  }
});

/**
 * When a staff message is deleted in a modmail thread, delete it from the database as well
 */
bot.on('messageDelete', async msg => {
  if (! msg.author) return;
  if (msg.author.bot) return;
  if (! utils.messageIsOnInboxServer(msg)) return;
  if (! utils.isStaff(msg.member)) return;

  const thread = await threads.findOpenThreadByChannelId(msg.channel.id);
  if (! thread) return;

  thread.deleteChatMessage(msg.id);
});

/**
 * When the bot is mentioned on the main server, ping staff in the log channel about it
 */
bot.on('messageCreate', async msg => {
  if (! utils.messageIsOnMainServer(msg)) return;
  if (! msg.mentions.some(user => user.id === bot.user.id)) return;
  if (msg.author.bot) return;

  if (utils.messageIsOnInboxServer(msg)) {
    // For same server setups, check if the person who pinged modmail is staff. If so, ignore the ping.
    if (utils.isStaff(msg.member)) return;
  } else {
    // For separate server setups, check if the member is staff on the modmail server
    const inboxMember = utils.getInboxGuild().members.get(msg.author.id);
    if (inboxMember && utils.isStaff(inboxMember)) return;
  }

  // If the person who mentioned the bot is blocked, ignore them
  if (await blocked.isBlocked(msg.author.id)) return;

  let content;
  const mainGuilds = utils.getMainGuilds();
  const staffMention = (config.pingOnBotMention ? utils.getInboxMention() : '');

  if (mainGuilds.length === 1) {
    content = `${staffMention}Bot mentioned in ${msg.channel.mention} by **${msg.author.username}#${msg.author.discriminator}**: "${msg.cleanContent}"`;
  } else {
    content = `${staffMention}Bot mentioned in ${msg.channel.mention} (${msg.channel.guild.name}) by **${msg.author.username}#${msg.author.discriminator}**: "${msg.cleanContent}"`;
  }

  bot.createMessage(utils.getLogChannel(bot).id, {
    content,
    disableEveryone: false,
  });
});

module.exports = {
  async start() {
    // Load modules
    console.log('Loading modules...');
    await reply(bot);
    await close(bot);
    await logs(bot);
    await block(bot);
    await move(bot);
    await snippets(bot);
    await suspend(bot);
    await greeting(bot);
    await webserver(bot);
    await typingProxy(bot);
    await version(bot);
    await newthread(bot);
    await idModule(bot);
    await alert(bot);

    // Connect to Discord
    console.log('Connecting to Discord...');
    await bot.connect();
  }
};
<|endoftext|>


<commit_before>var fork = require('child_process').fork;

module.exports.addWork = addWork;

var workerQueue = [];
var worker;
var isWorking = false;
var callbacks = [];

//setup
createObserverForQueue();
createAWorker();

function addWork(work, callback) {
  callback = callback || function() {};
  callbacks.push(callback);
  workerQueue.push(work);
}

function createObserverForQueue() {
  Array.observe(workerQueue, function(changes) {
    if(changes[0].addedCount && !isWorking) {
      work();
    }
  });
}

function createAWorker() {
  worker = fork('./app_modules/worker.js');

  worker.on('message', function done(message) {
    isWorking = false;
    callbacks.shift()(message);

    if(workerQueue.length) {
      work();
    }
  });
}

function work() {
  var workName = workerQueue.shift();
  isWorking = true;

  worker.send(workName);
}

<commit_msg>WIP. Making the worker-manager.js a singleton.<commit_after>var fork = require('child_process').fork;

// module.exports.addWork = addWork;

// var workerQueue = [];
// var worker;
// var isWorking = false;
// var callbacks = [];

// //setup
// createObserverForQueue();
// createAWorker();

// function addWork(work, callback) {
//   callback = callback || function() {};
//   callbacks.push(callback);
//   workerQueue.push(work);
// }

// function createObserverForQueue() {
//   Array.observe(workerQueue, function(changes) {
//     if(changes[0].addedCount && !isWorking) {
//       work();
//     }
//   });
// }

// function createAWorker() {
//   worker = fork('./app_modules/worker.js');

//   worker.on('message', function done(message) {
//     isWorking = false;
//     callbacks.shift()(message);

//     if(workerQueue.length) {
//       work();
//     }
//   });
// }

// function work() {
//   var workName = workerQueue.shift();
//   isWorking = true;

//   worker.send(workName);
// }

function QueueManager() {console.log('QueueManager init');
  var workerQueue = [];
  var worker;
  var isWorking = false;
  var callbacks = [];

  function createObserverForQueue() {
    Array.observe(workerQueue, function(changes) {
      if(changes[0].addedCount && !isWorking) {
        work();
      }
    });
  }

  function createAWorker() {
    worker = fork('./app_modules/worker.js');

    // worker.on('message', function done(message) {
    //   isWorking = false;
    //   callbacks.shift()(message);

    //   if(workerQueue.length) {
    //     work();
    //   }
    // });
  }

  function work() {
    var workName = workerQueue.shift();
    isWorking = true;

    worker.send(workName);
  }

  createObserverForQueue();
  createAWorker();

  this.addWork = function(work, callback) {
    callback = callback || function() {};
    callbacks.push(callback);
    workerQueue.push(work);
  }
}

module.exports = exports = new QueueManager();

// module.exports = exports = function() {
//   console.log('wtf');
// }();

<|endoftext|>


<commit_before>#!/usr/bin/env node

/*
 *
 * Diff to HTML CLI (main.js)
 * Author: rtfpessoa
 *
 */

var cli = require('./cli.js').Diff2HtmlInterface;
var log = require('./logger.js').Logger;
var utils = require('./utils.js').Utils;

var yargs = require('yargs');

var currentYear = new Date().getFullYear();

var argv = yargs.usage('Usage: diff2html [options] -- [diff args]')
  .options({
    's': {
      alias: 'style',
      describe: 'Output style',
      nargs: 1,
      type: 'string',
      choices: ['line', 'side'],
      default: 'line'
    }
  })
  .options({
    'sc': {
      alias: 'synchronisedScroll',
      describe: 'Synchronised horizontal scroll',
      type: 'string',
      choices: ['enabled', 'disabled'],
      default: 'disabled'
    }
  })
  .options({
    'su': {
      alias: 'summary',
      describe: 'Show files summary',
      type: 'string',
      choices: ['closed', 'open', 'hidden'],
      default: 'closed'
    }
  })
  .options({
    'lm': {
      alias: 'matching',
      describe: 'Diff line matching type',
      nargs: 1,
      type: 'string',
      choices: ['lines', 'words', 'none'],
      default: 'none'
    }
  })
  .options({
    'lmt': {
      alias: 'matchWordsThreshold',
      describe: 'Diff line matching word threshold',
      nargs: 1,
      type: 'string',
      default: '0.25'
    }
  })
  .options({
    'lmm': {
      alias: 'matchingMaxComparisons',
      describe: 'Diff line matching maximum line comparisons of a block of changes',
      nargs: 1,
      type: 'string',
      default: '1000'
    }
  })
  .options({
    'f': {
      alias: 'format',
      describe: 'Output format',
      nargs: 1,
      type: 'string',
      choices: ['html', 'json'],
      default: 'html'
    }
  })
  .options({
    'd': {
      alias: 'diff',
      describe: 'Diff style',
      nargs: 1,
      type: 'string',
      choices: ['word', 'char'],
      default: 'word'
    }
  })
  .options({
    'i': {
      alias: 'input',
      describe: 'Diff input source',
      nargs: 1,
      type: 'string',
      choices: ['file', 'command', 'stdin'],
      default: 'command'
    }
  })
  .options({
    'o': {
      alias: 'output',
      describe: 'Output destination',
      nargs: 1,
      type: 'string',
      choices: ['preview', 'stdout'],
      default: 'preview'
    }
  })
  .options({
    'u': {
      alias: 'diffy',
      describe: 'Upload to diffy.org',
      nargs: 1,
      type: 'string',
      choices: ['browser', 'pbcopy', 'print']
    }
  })
  .options({
    'F': {
      alias: 'file',
      describe: 'Send output to file (overrides output option)',
      nargs: 1,
      type: 'string'
    }
  })
  .example('diff2html -s line -f html -d word -i command -o preview -- -M HEAD~1',
    'diff last commit, line by line, word comparison between lines,' +
    'previewed in the browser and input from git diff command')
  .example('diff2html -i file -- my-file-diff.diff', 'reading the input from a file')
  .example('diff2html -f json -o stdout -- -M HEAD~1', 'print json format to stdout')
  .example('diff2html -F my-pretty-diff.html -- -M HEAD~1', 'print to file')
  .version(function() {
    return require('../package').version;
  })
  .help('h')
  .alias('h', 'help')
  .epilog('© 2014-' + currentYear + ' rtfpessoa\n' +
    'For more information, check out https://diff2html.xyz/\n' +
    'For support, check out https://github.com/rtfpessoa/diff2html-cli')
  .argv;

/*
 * CLI code
 */

function noop(ignore) {
  return ignore;
}

function onInput(err, input) {
  if (err) {
    log.error(err);
    return;
  }

  if (!input) {
    log.error('The input is empty. Try again.');
    yargs.showHelp('log');
    return;
  }

  if (argv.diffy) {
    cli.postToDiffy(input, argv.diffy, noop);
    return;
  }

  cli.getOutput(argv, input, onOutput);
}

function onOutput(err, output) {
  if (err) {
    log.error(err);
    return;
  }

  if (argv.file) {
    utils.writeFile(argv.file, output);
  } else if (argv.output === 'preview') {
    cli.preview(output, argv.format);
  } else {
    log.print(output);
  }
}

cli.getInput(argv.input, argv._, onInput);
<commit_msg>Fix version flag<commit_after>#!/usr/bin/env node

/*
 *
 * Diff to HTML CLI (main.js)
 * Author: rtfpessoa
 *
 */

var cli = require('./cli.js').Diff2HtmlInterface;
var log = require('./logger.js').Logger;
var utils = require('./utils.js').Utils;

var yargs = require('yargs');

var currentYear = new Date().getFullYear();

var argv = yargs.usage('Usage: diff2html [options] -- [diff args]')
  .options({
    's': {
      alias: 'style',
      describe: 'Output style',
      nargs: 1,
      type: 'string',
      choices: ['line', 'side'],
      default: 'line'
    }
  })
  .options({
    'sc': {
      alias: 'synchronisedScroll',
      describe: 'Synchronised horizontal scroll',
      type: 'string',
      choices: ['enabled', 'disabled'],
      default: 'disabled'
    }
  })
  .options({
    'su': {
      alias: 'summary',
      describe: 'Show files summary',
      type: 'string',
      choices: ['closed', 'open', 'hidden'],
      default: 'closed'
    }
  })
  .options({
    'lm': {
      alias: 'matching',
      describe: 'Diff line matching type',
      nargs: 1,
      type: 'string',
      choices: ['lines', 'words', 'none'],
      default: 'none'
    }
  })
  .options({
    'lmt': {
      alias: 'matchWordsThreshold',
      describe: 'Diff line matching word threshold',
      nargs: 1,
      type: 'string',
      default: '0.25'
    }
  })
  .options({
    'lmm': {
      alias: 'matchingMaxComparisons',
      describe: 'Diff line matching maximum line comparisons of a block of changes',
      nargs: 1,
      type: 'string',
      default: '1000'
    }
  })
  .options({
    'f': {
      alias: 'format',
      describe: 'Output format',
      nargs: 1,
      type: 'string',
      choices: ['html', 'json'],
      default: 'html'
    }
  })
  .options({
    'd': {
      alias: 'diff',
      describe: 'Diff style',
      nargs: 1,
      type: 'string',
      choices: ['word', 'char'],
      default: 'word'
    }
  })
  .options({
    'i': {
      alias: 'input',
      describe: 'Diff input source',
      nargs: 1,
      type: 'string',
      choices: ['file', 'command', 'stdin'],
      default: 'command'
    }
  })
  .options({
    'o': {
      alias: 'output',
      describe: 'Output destination',
      nargs: 1,
      type: 'string',
      choices: ['preview', 'stdout'],
      default: 'preview'
    }
  })
  .options({
    'u': {
      alias: 'diffy',
      describe: 'Upload to diffy.org',
      nargs: 1,
      type: 'string',
      choices: ['browser', 'pbcopy', 'print']
    }
  })
  .options({
    'F': {
      alias: 'file',
      describe: 'Send output to file (overrides output option)',
      nargs: 1,
      type: 'string'
    }
  })
  .example('diff2html -s line -f html -d word -i command -o preview -- -M HEAD~1',
    'diff last commit, line by line, word comparison between lines,' +
    'previewed in the browser and input from git diff command')
  .example('diff2html -i file -- my-file-diff.diff', 'reading the input from a file')
  .example('diff2html -f json -o stdout -- -M HEAD~1', 'print json format to stdout')
  .example('diff2html -F my-pretty-diff.html -- -M HEAD~1', 'print to file')
  .help('h')
  .alias('h', 'help')
  .epilog('© 2014-' + currentYear + ' rtfpessoa\n' +
    'For more information, check out https://diff2html.xyz/\n' +
    'For support, check out https://github.com/rtfpessoa/diff2html-cli')
  .argv;

/*
 * CLI code
 */

function noop(ignore) {
  return ignore;
}

function onInput(err, input) {
  if (err) {
    log.error(err);
    return;
  }

  if (!input) {
    log.error('The input is empty. Try again.');
    yargs.showHelp('log');
    return;
  }

  if (argv.diffy) {
    cli.postToDiffy(input, argv.diffy, noop);
    return;
  }

  cli.getOutput(argv, input, onOutput);
}

function onOutput(err, output) {
  if (err) {
    log.error(err);
    return;
  }

  if (argv.file) {
    utils.writeFile(argv.file, output);
  } else if (argv.output === 'preview') {
    cli.preview(output, argv.format);
  } else {
    log.print(output);
  }
}

cli.getInput(argv.input, argv._, onInput);
<|endoftext|>


<commit_before>describe('UVM allowed globals', function () {
    var testrun,
        globals = ['isNaN', 'SyntaxError', 'ArrayBuffer', 'JSON', 'unescape', 'URIError', 'TypeError', 'WeakSet',
            'Array', 'parseFloat', 'EvalError', 'parseInt', 'Error', 'Object', 'Scope', 'Int16Array',
            'NaN', 'Uint8ClampedArray', 'Uint32Array', 'Date', 'Intl', 'ReferenceError', 'PostmanApi',
            'Uint16Array', 'Int8Array', 'Boolean', 'RangeError', 'isFinite', 'encodeURIComponent', 'encodeURI',
            'decodeURI', 'String', 'undefined', 'Symbol', 'Set', 'WeakMap', 'Math', 'DataView', 'Int32Array',
            'Function', 'Number', 'Infinity', 'Promise', 'Float64Array', 'Float32Array', 'Map', 'RegExp',
            'decodeURIComponent', 'escape', 'Uint8Array', 'globals', 'environment', 'data',
            'iteration', 'request', 'responseCookies', 'responseBody', 'responseCode', 'responseHeaders',
            'responseTime', 'tests', '_', 'CryptoJS', 'tv4', 'cheerio', 'Backbone', 'xml2js', 'atob', 'btoa', 'Buffer',
            'xml2Json', 'postman', 'setTimeout', 'setInterval', 'XMLHttpRequest', 'console'];

    // add `Proxy` and `Reflect` objects for Node v6, which has them globally available.
    (typeof Proxy !== 'undefined') && globals.push('Proxy');
    (typeof Reflect !== 'undefined') && globals.push('Reflect');

    before(function (done) {
        this.run({
            collection: {
                item: {
                    name: 'r3',
                    event: [{
                        listen: 'test',
                        script: {
                            type: 'text/javascript',
                            exec: 'console.log(Object.keys(this));'
                        }
                    }],
                    request: 'echo.getpostman.com/get'
                }
            }
        }, function (err, results) {
            testrun = results;
            done(err);
        });
    });

    it('must have started and completed the test run', function () {
        expect(testrun).be.ok();
        expect(testrun.done.calledOnce).be.ok();
        expect(testrun.start.calledOnce).be.ok();
    });

    it.skip('must have run the test script, and had no extra globals', function () {
        expect(testrun.console.calledOnce).be.ok();

        var args = testrun.console.getCall(0).args;
        expect(args[1]).to.be('log');
        expect(args[2].sort()).to.eql(globals.sort());
    });
});
<commit_msg>Fixed tests for sandbox globals. - unwanted globals removed - xml2js is now in require and not global - set and clear immediate added - window specific variables removed<commit_after>describe('UVM allowed globals', function () {
    var testrun,
        globals = ['isNaN', 'SyntaxError', 'ArrayBuffer', 'JSON', 'unescape', 'URIError', 'TypeError', 'WeakSet',
            'Array', 'parseFloat', 'EvalError', 'parseInt', 'Error', 'Object', 'Int16Array',
            'NaN', 'Uint8ClampedArray', 'Uint32Array', 'Date', 'ReferenceError', 'Proxy', 'Reflect',
            'Uint16Array', 'Int8Array', 'Boolean', 'RangeError', 'isFinite', 'encodeURIComponent', 'encodeURI',
            'decodeURI', 'String', 'undefined', 'Symbol', 'Set', 'WeakMap', 'Math', 'DataView', 'Int32Array',
            'Function', 'Number', 'Infinity', 'Promise', 'Float64Array', 'Float32Array', 'Map', 'RegExp',
            'decodeURIComponent', 'escape', 'Uint8Array', 'globals', 'environment', 'data',
            'iteration', 'request', 'responseCookies', 'responseBody', 'responseCode', 'responseHeaders',
            'responseTime', 'tests', '_', 'CryptoJS', 'tv4', 'cheerio', 'Backbone', 'atob', 'btoa', 'Buffer',
            'xml2Json', 'postman', 'setTimeout', 'setInterval', 'setImmediate', 'clearTimeout', 'clearInterval',
            'clearImmediate', 'console'];

    if (typeof window !== 'undefined') {
        globals.push('XMLHttpRequest');
        globals.push('window');
        globals.push('document');
        globals.push('$');
        globals.push('jQuery');
    }

    before(function (done) {
        this.run({
            collection: {
                item: {
                    name: 'r3',
                    event: [{
                        listen: 'test',
                        script: {
                            type: 'text/javascript',
                            exec: 'console.log(Object.keys(this));'
                        }
                    }],
                    request: 'echo.getpostman.com/get'
                }
            }
        }, function (err, results) {
            testrun = results;
            done(err);
        });
    });

    it('must have started and completed the test run', function () {
        expect(testrun).be.ok();
        expect(testrun.done.calledOnce).be.ok();
        expect(testrun.start.calledOnce).be.ok();
    });

    it('must have run the test script, and had no extra globals', function () {
        expect(testrun.console.calledOnce).be.ok();

        var args = testrun.console.getCall(0).args;
        expect(args[1]).to.be('log');
        expect(args[2].sort()).to.eql(globals.sort());
    });
});
<|endoftext|>


<commit_before>var querry, editor, map;
var examples_file = "data/examples.json";
var style_file = "data/default.yaml";
var style_data = "";
var widgets = [];

var dragX = window.innerWidth/2.;

// CODE EDITOR
//----------------------------------------------
function initEditor(){
    querry = parseQuery(window.location.search.slice(1));

    if (querry['style']){
        style_file = querry['style'];
    }

    // Load style_file into style content
    style_data = fetchHTTP(style_file);

    var demoEditor = document.getElementById("editor");
    if(demoEditor){
        editor = CodeMirror(demoEditor,{
            value: style_data,
            lineNumbers: true,
            matchBrackets: true,
            // mode: "text/x-yaml",
            mode: "text/x-yaml-tangram",
            keyMap: "sublime",
            autoCloseBrackets: true,
            extraKeys: {"Ctrl-Space": "autocomplete",
                        "Tab": function(cm) {
                            var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
                            cm.replaceSelection(spaces);
                        },
                        "Alt-F" : function(cm) {
                                        var pos = cm.getCursor();
                                        var opts = cm.state.foldGutter.options;
                                        cm.foldCode(pos, opts.rangeFinder);
                                    } ,
                        "Ctrl-0" : function(cm){unfoldAll(cm)},
                        "Ctrl-1" : function(cm){foldByLevel(cm,0)},
                        "Ctrl-2" : function(cm){foldByLevel(cm,1)},
                        "Ctrl-3" : function(cm){foldByLevel(cm,2)},
                        "Ctrl-4" : function(cm){foldByLevel(cm,3)},
                        "Ctrl-5" : function(cm){foldByLevel(cm,4)},
                        "Ctrl-6" : function(cm){foldByLevel(cm,5)},
                        "Ctrl-7" : function(cm){foldByLevel(cm,6)},
                        "Ctrl-8" : function(cm){foldByLevel(cm,7)}
            },
            foldGutter: {
                rangeFinder: CodeMirror.fold.indent
            },
            gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
            showCursorWhenSelecting: true,
            theme: "tangram",
            lineWrapping: true,
            autofocus: true,
            indentUnit: 4
        });

        editor.on("change", function(cm){
            var updateContet = debounce( function(){
                var createObjectURL = (window.URL && window.URL.createObjectURL) || (window.webkitURL && window.webkitURL.createObjectURL); // for Safari compatibliity
                var url = createObjectURL(new Blob([ editor.getValue() ]));
                scene.reload(url);

                updateWidgets();
            }, 500);
            updateContet();
        });

        editor.on("viewportChange", function(cm){
            var updateViewPort = debounce( function(){
                updateWidgets();
            },100);
            updateViewPort();
        });

        demoEditor.addEventListener("mousedown", onClick);
    }

    Draggable.create("#divider", {
        type: "x",
        bounds: document.getElementById("wrapper"),
        onDrag: function (pointerEvent) {
            console.log(pointerEvent);
            dragX = pointerEvent.clientX;
            var width = document.getElementById('divider').offsetWidth;
            document.getElementById('map').style.width = (dragX) + "px";
            document.getElementById('content').style.marginLeft = document.getElementById('map').offsetWidth + "px";
            document.getElementById('content').style.width =  (window.innerWidth - dragX) + "px"
            editor.setSize('100%',(window.innerHeight-31) + 'px');
            map.invalidateSize(false);
        }
    });
}

function loadExamples() {
    var examples_data = JSON.parse(fetchHTTP("data/examples.json"));
    var examplesList = document.getElementById("examples");

    for (var i = 0; i < examples_data['examples'].length; i++ ){
        var example = examples_data['examples'][i];
        var newOption = document.createElement("option");
        newOption.value = example['url'];
        newOption.innerHTML= example['name'];
        examplesList.appendChild(newOption);
    }
}

function loadWidgets(){
    var widgets_data = JSON.parse(fetchHTTP("data/widgets.json"));

    for (var i = 0; i < widgets_data["widgets"].length; i++){
        var obj = {};
        obj.type = widgets_data["widgets"][i].type;
        obj.token = addWidgetToken(widgets_data["widgets"][i]);

        if ( obj.type === "dropdownmenu" ){
            obj.options = widgets_data["widgets"][i].options;
        }

        widgets.push( obj );
    }
}

function addWidgetToken( widgetOBJ ){
    var token;
    if ( widgetOBJ['address'] ){
        token = function(scene, cm, nLine) {
            return RegExp( widgetOBJ['address'] ).test( getTagAddress(cm, nLine) );
        };
    } else if ( widgetOBJ['tag'] ){
        token = function(scene, cm, nLine) {
            return RegExp( widgetOBJ['tag'] ).test( getTag(cm, nLine) );
        };
    } else if ( widgetOBJ['value'] ){
        token = function(scene, cm, nLine) {
            return RegExp( widgetOBJ['value'] ).test( getValue(cm, nLine) );
        };
    } else if ( widgetOBJ['content'] ){
        token = function(scene, cm, nLine) {
            return RegExp( widgetOBJ['content'] ).test( getTagCompleteContent(scene, cm, nLine) );
        };
    } else {
        token = function(scene, cm, nLine) {
            return false;
        };
    }
    return token;
}

function updateWidgets(){
    var colorpickers = document.getElementsByClassName("widget");
    for (var i = colorpickers.length-1; i >=0 ; i--){
        colorpickers[i].parentNode.removeChild(colorpickers[i]);
    }

    for (var nline = 0; nline < editor.doc.size; nline++){
        var val = getValue(editor,nline);

        // If Line is significative
        if (getTag(editor,nline) !== "" && val !== "|" && val !== "" ){
            // Chech for Colors
            for (var i = 0; i < widgets.length; i++){
                if ( widgets[i].token(scene,editor,nline) ){
                    var content = getValue(editor, nline);

                    if (widgets[i].type === "colorpicker"){
                        var btn = document.createElement("div");
                        btn.style.zIndex = "10";
                        btn.style.background = toCSS(content);
                        btn.className = "widget";
                        btn.style.border = "1px solid #A8ABAA";
                        btn.style.borderRadius = "4px";
                        editor.addWidget({line:nline, ch:editor.lineInfo(nline).handle.text.length }, btn);
                        btn.style.top = (parseInt(btn.style.top, 10) - 17)+"px";
                        btn.style.left = (parseInt(btn.style.left, 10) + 5)+"px";
                        btn.style.width = "17px";
                        btn.style.height = "17px";
                        break;
                    } else if (widgets[i].type === "dropdownmenu"){

                        var list = document.createElement('Select');
                        list.className = "widget";
                        list.style.zIndex = "10";

                        var selected = -1;
                        for (var j = 0; j < widgets[i].options.length ; j++ ){
                            var newOption = document.createElement("option");
                            newOption.value = nline;
                            if (content === widgets[i].options[j]) {
                                newOption.selected = true;
                            }
                            newOption.innerHTML= widgets[i].options[j];
                            list.appendChild(newOption);
                        }

                        editor.addWidget({line:nline, ch:editor.lineInfo(nline).handle.text.length }, list);
                        list.style.top = (parseInt(list.style.top, 10) - 17)+"px";
                        list.style.left = (parseInt(list.style.left, 10) + 5)+"px";
                        list.setAttribute('onchange','dropdownMenuChange(this)');
                        break;
                    } else if (widgets[i].type === "togglebutton"){
                        var check = document.createElement('Input');
                        check.type = 'checkbox';
                        check.className = "widget";
                        check.style.zIndex = "10";
                        check.value = nline;
                        check.checked = getValue(editor,nline) === "true" ? true : false;
                        editor.addWidget({line:nline, ch:editor.lineInfo(nline).handle.text.length }, check);
                        check.style.top = (parseInt(check.style.top, 10) - 17)+"px";
                        check.style.left = (parseInt(check.style.left, 10) + 5)+"px";
                        check.setAttribute('onchange','toggleButton(this)');
                        break;
                    }

                }
            }
        }
    }
}

// TANGRAM
//----------------------------------------------
function initMap() {
    'use strict';
    var map_start_location = [40.70531887544228, -74.00976419448853, 16];

    /*** URL parsing ***/

    // leaflet-style URL hash pattern:
    // ?style.yaml#[zoom],[lat],[lng]
    var url_hash = window.location.hash.slice(1).split('/');
    if (url_hash.length == 3) {
        map_start_location = [url_hash[1],url_hash[2], url_hash[0]];
        // convert from strings
        map_start_location = map_start_location.map(Number);
    }

    /*** Map ***/
    map = L.map('map',
        { zoomControl: false },
        {'keyboardZoomOffset': .05}
    );

    L.control.zoom({ position: 'topright' }).addTo(map);

    map.attributionControl.setPrefix('Leaflet')
    var layer = Tangram.leafletLayer({
        scene: style_file,
        attribution: 'Tangram | © OSM contributors | Mapzen'
    });

    window.layer = layer;
    var scene = layer.scene;
    window.scene = scene;

    map.setView(map_start_location.slice(0, 2), map_start_location[2]);
    var hash = new L.Hash(map);

    window.addEventListener('load', function () {
        // Scene initialized
        layer.addTo(map);
    });

    //disable mousewheel zoom if iframed
    if (window.self !== window.top) {
      map.scrollWheelZoom.disable();
    }
}

// Events
//----------------------------------------------
function newContent(){
    window.location.href = ".";
}

function openExample(select){
    var option = select.options[select.selectedIndex].value;
    window.location.href = ".?style="+option;
}

function dropdownMenuChange(select) {
    setValue(   editor,
                parseInt(select.options[select.selectedIndex].value),
                select.options[select.selectedIndex].innerHTML );
}

function toggleButton(check) {
    setValue(   editor,
                parseInt(check.value),
                check.checked?"true":"false" );
}

function openContent(input){
    var reader = new FileReader();
    reader.onload = function(e) {
        editor.setValue(e.target.result);
    }
    reader.readAsText(input.files[0]);
}

function saveContent(){
    if (editor) {
        var blob = new Blob([editor.getValue()], {type: "text/plain;charset=utf-8"});
        saveAs(blob, "style.yaml");
    }
}

function resizeMap() {
    var width = document.getElementById('divider').offsetWidth;
    document.getElementById('map').style.width = (dragX) + "px";
    document.getElementById('divider').style.left = (dragX-width/2) + "px";
    document.getElementById('content').style.marginLeft = document.getElementById('map').offsetWidth + "px";
    document.getElementById('content').style.width =  (window.innerWidth - dragX) + "px"
    editor.setSize('100%',(window.innerHeight-31) + 'px');
    map.invalidateSize(false);
    updateWidgets()
}

function onClick(event) {
    // var cursor = editor.getCursor(true);

    // console.log( ">>> " + getTagAddress(editor, cursor.line) );

    // if ( isCommented(editor,cursor.line) ){
    //     console.log("Comented line");
    // }

    // var address = getTagAddress(editor, cursor.line);

    // if ( isGlobalBlock(address) ){
    //     console.log("GLOBAL Shader Block");
    // } else if ( isColorBlock(address) ){
    //     console.log("COLOR Shader Block");
    // } else if ( isNormalBlock(address) ){
    //     console.log("NORMAL Shader Block");
    // } else if ( isFilterBlock(address) ){
    //     console.log("FILTER Shader Block");
    // }

    // console.log( getTagCompleteContent(scene, editor, cursor.line) );
}

window.addEventListener('resize', resizeMap);

// MAIN
//----------------------------------------------
initEditor();
initMap();

resizeMap();
loadExamples();
loadWidgets();

// Once everything is loaded
setTimeout(function () {
    if (querry['foldLevel']){
        unfoldAll(editor);
        foldByLevel(editor,parseInt(querry['foldLevel']));
    }
    if (querry['lines']){
        selectLines(editor,querry['lines']);
    }

    updateWidgets();
}, 1000);
<commit_msg>Divider stays in place if window resizes. Workaround for map flickering during resize<commit_after>var querry, editor, map;
var examples_file = "data/examples.json";
var style_file = "data/default.yaml";
var style_data = "";
var widgets = [];

// CODE EDITOR
//----------------------------------------------
function initEditor(){
    querry = parseQuery(window.location.search.slice(1));

    if (querry['style']){
        style_file = querry['style'];
    }

    // Load style_file into style content
    style_data = fetchHTTP(style_file);

    var demoEditor = document.getElementById("editor");
    if(demoEditor){
        editor = CodeMirror(demoEditor,{
            value: style_data,
            lineNumbers: true,
            matchBrackets: true,
            // mode: "text/x-yaml",
            mode: "text/x-yaml-tangram",
            keyMap: "sublime",
            autoCloseBrackets: true,
            extraKeys: {"Ctrl-Space": "autocomplete",
                        "Tab": function(cm) {
                            var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
                            cm.replaceSelection(spaces);
                        },
                        "Alt-F" : function(cm) {
                                        var pos = cm.getCursor();
                                        var opts = cm.state.foldGutter.options;
                                        cm.foldCode(pos, opts.rangeFinder);
                                    } ,
                        "Ctrl-0" : function(cm){unfoldAll(cm)},
                        "Ctrl-1" : function(cm){foldByLevel(cm,0)},
                        "Ctrl-2" : function(cm){foldByLevel(cm,1)},
                        "Ctrl-3" : function(cm){foldByLevel(cm,2)},
                        "Ctrl-4" : function(cm){foldByLevel(cm,3)},
                        "Ctrl-5" : function(cm){foldByLevel(cm,4)},
                        "Ctrl-6" : function(cm){foldByLevel(cm,5)},
                        "Ctrl-7" : function(cm){foldByLevel(cm,6)},
                        "Ctrl-8" : function(cm){foldByLevel(cm,7)}
            },
            foldGutter: {
                rangeFinder: CodeMirror.fold.indent
            },
            gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
            showCursorWhenSelecting: true,
            theme: "tangram",
            lineWrapping: true,
            autofocus: true,
            indentUnit: 4
        });

        editor.on("change", function(cm){
            var updateContet = debounce( function(){
                var createObjectURL = (window.URL && window.URL.createObjectURL) || (window.webkitURL && window.webkitURL.createObjectURL); // for Safari compatibliity
                var url = createObjectURL(new Blob([ editor.getValue() ]));
                scene.reload(url);

                updateWidgets();
            }, 500);
            updateContet();
        });

        editor.on("viewportChange", function(cm){
            var updateViewPort = debounce( function(){
                updateWidgets();
            },100);
            updateViewPort();
        });

        demoEditor.addEventListener("mousedown", onClick);
    }

    Draggable.create("#divider", {
        type: "x",
        bounds: document.getElementById("wrapper"),
        onDrag: function (pointerEvent) {
            var mapEl = document.getElementById('map');
            var contentEl = document.getElementById('content');
            var dividerEl = document.getElementById('divider');
            var positionX = dividerEl.getBoundingClientRect().left;

            mapEl.style.width = positionX + "px";
            contentEl.style.marginLeft = mapEl.offsetWidth + "px";
            contentEl.style.width = (window.innerWidth - positionX) + "px";

            editor.setSize('100%', (window.innerHeight - 31) + 'px');
            //map.invalidateSize(false);
        },
        onDragEnd: function () {
            map.invalidateSize(false);
        }
    });
}

function loadExamples() {
    var examples_data = JSON.parse(fetchHTTP("data/examples.json"));
    var examplesList = document.getElementById("examples");

    for (var i = 0; i < examples_data['examples'].length; i++ ){
        var example = examples_data['examples'][i];
        var newOption = document.createElement("option");
        newOption.value = example['url'];
        newOption.innerHTML= example['name'];
        examplesList.appendChild(newOption);
    }
}

function loadWidgets(){
    var widgets_data = JSON.parse(fetchHTTP("data/widgets.json"));

    for (var i = 0; i < widgets_data["widgets"].length; i++){
        var obj = {};
        obj.type = widgets_data["widgets"][i].type;
        obj.token = addWidgetToken(widgets_data["widgets"][i]);

        if ( obj.type === "dropdownmenu" ){
            obj.options = widgets_data["widgets"][i].options;
        }

        widgets.push( obj );
    }
}

function addWidgetToken( widgetOBJ ){
    var token;
    if ( widgetOBJ['address'] ){
        token = function(scene, cm, nLine) {
            return RegExp( widgetOBJ['address'] ).test( getTagAddress(cm, nLine) );
        };
    } else if ( widgetOBJ['tag'] ){
        token = function(scene, cm, nLine) {
            return RegExp( widgetOBJ['tag'] ).test( getTag(cm, nLine) );
        };
    } else if ( widgetOBJ['value'] ){
        token = function(scene, cm, nLine) {
            return RegExp( widgetOBJ['value'] ).test( getValue(cm, nLine) );
        };
    } else if ( widgetOBJ['content'] ){
        token = function(scene, cm, nLine) {
            return RegExp( widgetOBJ['content'] ).test( getTagCompleteContent(scene, cm, nLine) );
        };
    } else {
        token = function(scene, cm, nLine) {
            return false;
        };
    }
    return token;
}

function updateWidgets(){
    var colorpickers = document.getElementsByClassName("widget");
    for (var i = colorpickers.length-1; i >=0 ; i--){
        colorpickers[i].parentNode.removeChild(colorpickers[i]);
    }

    for (var nline = 0; nline < editor.doc.size; nline++){
        var val = getValue(editor,nline);

        // If Line is significative
        if (getTag(editor,nline) !== "" && val !== "|" && val !== "" ){
            // Chech for Colors
            for (var i = 0; i < widgets.length; i++){
                if ( widgets[i].token(scene,editor,nline) ){
                    var content = getValue(editor, nline);

                    if (widgets[i].type === "colorpicker"){
                        var btn = document.createElement("div");
                        btn.style.zIndex = "10";
                        btn.style.background = toCSS(content);
                        btn.className = "widget";
                        btn.style.border = "1px solid #A8ABAA";
                        btn.style.borderRadius = "4px";
                        editor.addWidget({line:nline, ch:editor.lineInfo(nline).handle.text.length }, btn);
                        btn.style.top = (parseInt(btn.style.top, 10) - 17)+"px";
                        btn.style.left = (parseInt(btn.style.left, 10) + 5)+"px";
                        btn.style.width = "17px";
                        btn.style.height = "17px";
                        break;
                    } else if (widgets[i].type === "dropdownmenu"){

                        var list = document.createElement('Select');
                        list.className = "widget";
                        list.style.zIndex = "10";

                        var selected = -1;
                        for (var j = 0; j < widgets[i].options.length ; j++ ){
                            var newOption = document.createElement("option");
                            newOption.value = nline;
                            if (content === widgets[i].options[j]) {
                                newOption.selected = true;
                            }
                            newOption.innerHTML= widgets[i].options[j];
                            list.appendChild(newOption);
                        }

                        editor.addWidget({line:nline, ch:editor.lineInfo(nline).handle.text.length }, list);
                        list.style.top = (parseInt(list.style.top, 10) - 17)+"px";
                        list.style.left = (parseInt(list.style.left, 10) + 5)+"px";
                        list.setAttribute('onchange','dropdownMenuChange(this)');
                        break;
                    } else if (widgets[i].type === "togglebutton"){
                        var check = document.createElement('Input');
                        check.type = 'checkbox';
                        check.className = "widget";
                        check.style.zIndex = "10";
                        check.value = nline;
                        check.checked = getValue(editor,nline) === "true" ? true : false;
                        editor.addWidget({line:nline, ch:editor.lineInfo(nline).handle.text.length }, check);
                        check.style.top = (parseInt(check.style.top, 10) - 17)+"px";
                        check.style.left = (parseInt(check.style.left, 10) + 5)+"px";
                        check.setAttribute('onchange','toggleButton(this)');
                        break;
                    }

                }
            }
        }
    }
}

// TANGRAM
//----------------------------------------------
function initMap() {
    'use strict';
    var map_start_location = [40.70531887544228, -74.00976419448853, 16];

    /*** URL parsing ***/

    // leaflet-style URL hash pattern:
    // ?style.yaml#[zoom],[lat],[lng]
    var url_hash = window.location.hash.slice(1).split('/');
    if (url_hash.length == 3) {
        map_start_location = [url_hash[1],url_hash[2], url_hash[0]];
        // convert from strings
        map_start_location = map_start_location.map(Number);
    }

    /*** Map ***/
    map = L.map('map',
        { zoomControl: false },
        {'keyboardZoomOffset': .05}
    );

    L.control.zoom({ position: 'topright' }).addTo(map);

    map.attributionControl.setPrefix('Leaflet')
    var layer = Tangram.leafletLayer({
        scene: style_file,
        attribution: 'Tangram | © OSM contributors | Mapzen'
    });

    window.layer = layer;
    var scene = layer.scene;
    window.scene = scene;

    map.setView(map_start_location.slice(0, 2), map_start_location[2]);
    var hash = new L.Hash(map);

    window.addEventListener('load', function () {
        // Scene initialized
        layer.addTo(map);
    });

    //disable mousewheel zoom if iframed
    if (window.self !== window.top) {
      map.scrollWheelZoom.disable();
    }
}

// Events
//----------------------------------------------
function newContent(){
    window.location.href = ".";
}

function openExample(select){
    var option = select.options[select.selectedIndex].value;
    window.location.href = ".?style="+option;
}

function dropdownMenuChange(select) {
    setValue(   editor,
                parseInt(select.options[select.selectedIndex].value),
                select.options[select.selectedIndex].innerHTML );
}

function toggleButton(check) {
    setValue(   editor,
                parseInt(check.value),
                check.checked?"true":"false" );
}

function openContent(input){
    var reader = new FileReader();
    reader.onload = function(e) {
        editor.setValue(e.target.result);
    }
    reader.readAsText(input.files[0]);
}

function saveContent(){
    if (editor) {
        var blob = new Blob([editor.getValue()], {type: "text/plain;charset=utf-8"});
        saveAs(blob, "style.yaml");
    }
}

function resizeMap (event) {
    var mapEl = document.getElementById('map');
    var contentEl = document.getElementById('content');
    var dividerEl = document.getElementById('divider');

    if (!event) {
        dividerEl.style.left = Math.floor(window.innerWidth / 2) + "px";
    }

    var positionX = dividerEl.getBoundingClientRect().left;

    mapEl.style.width = positionX + "px";
    contentEl.style.marginLeft = mapEl.offsetWidth + "px";
    contentEl.style.width = (window.innerWidth - positionX) + "px";

    editor.setSize('100%', (window.innerHeight - 31) + 'px');
    map.invalidateSize(false);
    updateWidgets()
}

function onClick(event) {
    // var cursor = editor.getCursor(true);

    // console.log( ">>> " + getTagAddress(editor, cursor.line) );

    // if ( isCommented(editor,cursor.line) ){
    //     console.log("Comented line");
    // }

    // var address = getTagAddress(editor, cursor.line);

    // if ( isGlobalBlock(address) ){
    //     console.log("GLOBAL Shader Block");
    // } else if ( isColorBlock(address) ){
    //     console.log("COLOR Shader Block");
    // } else if ( isNormalBlock(address) ){
    //     console.log("NORMAL Shader Block");
    // } else if ( isFilterBlock(address) ){
    //     console.log("FILTER Shader Block");
    // }

    // console.log( getTagCompleteContent(scene, editor, cursor.line) );
}

window.addEventListener('resize', resizeMap);

// MAIN
//----------------------------------------------
initEditor();
initMap();

resizeMap();
loadExamples();
loadWidgets();

// Once everything is loaded
setTimeout(function () {
    if (querry['foldLevel']){
        unfoldAll(editor);
        foldByLevel(editor,parseInt(querry['foldLevel']));
    }
    if (querry['lines']){
        selectLines(editor,querry['lines']);
    }

    updateWidgets();
}, 1000);
<|endoftext|>