<commit_before>/* ==============================================================================
 * Licensed under the Apache License, Version 2.0 (the 'License'); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
var range
describe('date range default behavior', function() {
  beforeEach(resetRange)

  it('creates range of three days', function() {
    expect(range.start).toEqual(start)
    expect(range.end).toEqual(end)
    expect(range.days()).toEqual(3)
    expect(range).not.toHaveDate('09/9/2009')
    expect(range).toHaveDate('09/10/2009')
    expect(range).toHaveDate('09/11/2009')
    expect(range).toHaveDate('09/12/2009')
    expect(range).not.toHaveDate('09/13/2009')
    expect(range.toString(DATE_LOCALE_FI)).toEqual('10.9.2009 - 12.9.2009')
  })

  it('range is movable', function() {
    range = range.shiftDays(2)
    expect(range.start.getDate()).toEqual(12)
    expect(range.end.getDate()).toEqual(12 + 2)
  })

  it('range is expandable', function() {
    range = range.expandTo(new Date('09/15/2009'))
    expect(range.days()).toEqual(6)
  })

  it('two ranges can do interception', function() {
    var range2 = new DateRange(new Date('09/11/2009'), new Date('09/19/2009'))
    expect(range.and(range2).days()).toEqual(2)
    range2 = new DateRange(new Date('09/16/2009'), new Date('09/19/2009'))
    expect(range.and(range2).days()).toEqual(0)
  })

  it('range can be asked if it is a subset of another range', function() {
    expect(range).toBeInside(range)
    expect(range).not.toBeInside(range.shiftDays(1))
    expect(range).toBeInside(range.expandDaysTo(7))
    expect(range.expandDaysTo(7)).not.toBeInside(range)
  })
})

describe('moving date range within outer range', function() {
  beforeEach(resetOuterRange)

  it('range already inside outer range is not moved', function() {
    var range1 = new DateRange(new Date('04/04/2011'), new Date('04/10/2011'))
    var range2 = range1.shiftInside(outerRange)
    expect(range1).toBeInside(outerRange)
    expect(range2).toBeInside(outerRange)
    expect(range1.start.getDate()).toEqual(range2.start.getDate())
    expect(range1.end.getDate()).toEqual(range2.end.getDate())
  })

  it('range can be moved forward inside outer range', function() {
    var range1 = new DateRange(new Date('03/15/2011'), new Date('03/21/2011'))
    var range2 = range1.shiftInside(outerRange)
    expect(range1).not.toBeInside(outerRange)
    expect(range2).toBeInside(outerRange)
    expect(range2.start.getDate()).toEqual(28)
    expect(range2.end.getDate()).toEqual(3)
  })

  it('range can be moved backward inside outer range', function() {
    var range1 = new DateRange(new Date('04/28/2011'), new Date('05/04/2011'))
    var range2 = range1.shiftInside(outerRange)
    expect(range1).not.toBeInside(outerRange)
    expect(range2).toBeInside(outerRange)
    expect(range2.start.getDate()).toEqual(25)
    expect(range2.end.getDate()).toEqual(1)
  })

  it('range longer than outer range cannot be moved', function() {
    var range1 = new DateRange(outerRange.start.plusDays(-1), outerRange.end.plusDays(1))
    var range2 = range1.shiftInside(outerRange)
    expect(range2.days()).toEqual(0)
  })
})

describe('date range with minimum size within outer range', function() {
  beforeEach(resetOuterRange)

  it('range can be requested near the beginning of outer range', function() {
    var oldRange = new DateRange(new Date('03/28/2011'), new Date('04/03/2011'))
    var newRange = DateRange.rangeWithMinimumSize(oldRange, 7, true, outerRange)
    expect(newRange.days()).toEqual(7)
    expect(newRange.start.getDate()).toEqual(29)
    expect(newRange.end.getDate()).toEqual(4)
  })

  it('range can be requested near the end of outer range', function() {
    var oldRange = new DateRange(new Date('04/25/2011'), new Date('04/25/2011'))
    var newRange = DateRange.rangeWithMinimumSize(oldRange, 7, false, outerRange)
    expect(newRange.days()).toEqual(7)
  })

  it('range cannot be requested to be outside outer range', function() {
    var oldRange = new DateRange(new Date('04/26/2011'), new Date('04/26/2011'))
    var newRange = DateRange.rangeWithMinimumSize(oldRange, 7, false, outerRange)
    expect(newRange.days()).toEqual(0)
  })

  it('range may not be found near the end of outer range due to weekends', function() {
    var oldRange = new DateRange(new Date('04/24/2011'), new Date('04/24/2011'))
    var newRange = DateRange.rangeWithMinimumSize(oldRange, 7, true, outerRange)
    expect(newRange.days()).toEqual(0)
  })
})

describe('date range with time behavior', function() {
  beforeEach(resetRange)

  it('date range can have times', function() {
    DATE_LOCALE_EN.init()
    range.setTimes('10:00', '14:45')
    expect(range.days()).toEqual(2)
    expect(range.hours()).toEqual(4)
    expect(range.minutes()).toEqual(45)
    expect(range.toString()).toEqual('2 Days 4.75 Hours')

    DATE_LOCALE_FI.init()
    expect(range.toString()).toEqual('2 päivää 4,75 tuntia')

    range.setTimes('17:00', '16:00')
    expect(range.days()).toEqual(1)
    expect(range.hours()).toEqual(23)
    expect(range.minutes()).toEqual(0)
    range.start = range.start.plusDays(1)

    range.setTimes('10:00', '11:00')
    expect(range.toString()).toEqual('1 päivä 1 tunti')

    DATE_LOCALE_EN.init()
    expect(range.toString()).toEqual('1 Day 1 Hour')
  })

  it('one day range with start time after end time is not valid', function() {
    expect(range).toBeValidRange()
    range.start = new Date('09/13/2009')
    expect(range).not.toBeValidRange()
    range.start = new Date('09/12/2009')
    expect(range).toBeValidRange()
    range.setTimes('15:00', '14:30')
    expect(range).not.toBeValidRange()
    range.setTimes('15:00', '15:00')
    expect(range).toBeValidRange()
    range.setTimes('15:00', '15:30')
    expect(range).toBeValidRange()
  })

  it('invalid time will make range invalid while keeping date information', function() {
    range.setTimes('15:00', '15:30')
    expect(range).toBeValidRange()

    range.setTimes('', '15:30')
    expect(range).not.toBeValidRange()

    range.setTimes('15:00', '15:30')
    expect(range).toBeValidRange()

    range.setTimes('asdf', 'fddd')
    expect(range).not.toBeValidRange()

    range.setTimes('00', '25')
    expect(range).not.toBeValidRange()

  })

  it('different time formats are accepted', function() {
    range.setTimes('15:00', '16:10')
    assertHasCorrectHoursAndMinutes(1, 10)

    range.setTimes('14.00', '16.20')
    assertHasCorrectHoursAndMinutes(2, 20)

    range.setTimes('13,00', '16,30')
    assertHasCorrectHoursAndMinutes(3, 30)

    range.setTimes('1200', '1640')
    assertHasCorrectHoursAndMinutes(4, 40)

    range.setTimes('830', '1240')
    assertHasCorrectHoursAndMinutes(4, 10)

    range.setTimes('08', '13')
    assertHasCorrectHoursAndMinutes(5, 0)
  })

  it('minutes are rounded to 2 digits', function() {
    range.setTimes('15:00', '16:10')
    assertHasCorrectHoursAndMinutes(1, 10)
    DATE_LOCALE_FI.init()
    expect(range.toString()).toEqual('2 päivää 1,17 tuntia')
  })

  it('range is displayed with the most defining unit', function() {
    range = new DateRange(new Date('01/01/2004'), new Date('05/01/2006'))
    expect(range).toPrintDefiningDurationOf('2 vuotta')
    range = new DateRange(new Date('01/01/2004'), new Date('05/01/2005'))
    expect(range).toPrintDefiningDurationOf('1 vuosi')
    range = new DateRange(new Date('01/01/2004'), new Date('05/01/2004'))
    expect(range).toPrintDefiningDurationOf('4 kuukautta')
    range = new DateRange(new Date('01/01/2004'), new Date('02/16/2004'))
    expect(range).toPrintDefiningDurationOf('1 kuukausi')
    range = new DateRange(new Date('01/01/2004'), new Date('01/31/2004'))
    expect(range).toPrintDefiningDurationOf('1 kuukausi')
    range = new DateRange(new Date('01/01/2004'), new Date('01/07/2004'))
    expect(range).toPrintDefiningDurationOf('7 päivää')
  })
})

function assertHasCorrectHoursAndMinutes(hours, minutes) {
  expect(range).toBeValidRange()
  expect(range.hours()).toEqual(hours)
  expect(range.minutes()).toEqual(minutes)
}

function resetRange() {
  start = new Date('09/10/2009')
  end = new Date('09/12/2009')
  range = new DateRange(end, start)
}

function resetOuterRange() {
  start = new Date('03/28/2011')
  end = new Date('05/01/2011')
  outerRange = new DateRange(start, end)
}
<commit_msg>Extract range creation<commit_after>/* ==============================================================================
 * Licensed under the Apache License, Version 2.0 (the 'License'); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
var range
describe('date range default behavior', function() {
  beforeEach(resetRange)

  it('creates range of three days', function() {
    expect(range.start).toEqual(start)
    expect(range.end).toEqual(end)
    expect(range.days()).toEqual(3)
    expect(range).not.toHaveDate('09/9/2009')
    expect(range).toHaveDate('09/10/2009')
    expect(range).toHaveDate('09/11/2009')
    expect(range).toHaveDate('09/12/2009')
    expect(range).not.toHaveDate('09/13/2009')
    expect(range.toString(DATE_LOCALE_FI)).toEqual('10.9.2009 - 12.9.2009')
  })

  it('range is movable', function() {
    range = range.shiftDays(2)
    expect(range.start.getDate()).toEqual(12)
    expect(range.end.getDate()).toEqual(12 + 2)
  })

  it('range is expandable', function() {
    range = range.expandTo(new Date('09/15/2009'))
    expect(range.days()).toEqual(6)
  })

  it('two ranges can do interception', function() {
    var range2 = createRange('09/11/2009', '09/19/2009')
    expect(range.and(range2).days()).toEqual(2)
    range2 = createRange('09/16/2009', '09/19/2009')
    expect(range.and(range2).days()).toEqual(0)
  })

  it('range can be asked if it is a subset of another range', function() {
    expect(range).toBeInside(range)
    expect(range).not.toBeInside(range.shiftDays(1))
    expect(range).toBeInside(range.expandDaysTo(7))
    expect(range.expandDaysTo(7)).not.toBeInside(range)
  })
})

describe('moving date range within outer range', function() {
  beforeEach(resetOuterRange)

  it('range already inside outer range is not moved', function() {
    var range1 = createRange('04/04/2011', '04/10/2011')
    var range2 = range1.shiftInside(outerRange)
    expect(range1).toBeInside(outerRange)
    expect(range2).toBeInside(outerRange)
    expect(range1.start.getDate()).toEqual(range2.start.getDate())
    expect(range1.end.getDate()).toEqual(range2.end.getDate())
  })

  it('range can be moved forward inside outer range', function() {
    var range1 = createRange('03/15/2011', '03/21/2011')
    var range2 = range1.shiftInside(outerRange)
    expect(range1).not.toBeInside(outerRange)
    expect(range2).toBeInside(outerRange)
    expect(range2.start.getDate()).toEqual(28)
    expect(range2.end.getDate()).toEqual(3)
  })

  it('range can be moved backward inside outer range', function() {
    var range1 = createRange('04/28/2011', '05/04/2011')
    var range2 = range1.shiftInside(outerRange)
    expect(range1).not.toBeInside(outerRange)
    expect(range2).toBeInside(outerRange)
    expect(range2.start.getDate()).toEqual(25)
    expect(range2.end.getDate()).toEqual(1)
  })

  it('range longer than outer range cannot be moved', function() {
    var range1 = new DateRange(outerRange.start.plusDays(-1), outerRange.end.plusDays(1))
    var range2 = range1.shiftInside(outerRange)
    expect(range2.days()).toEqual(0)
  })
})

describe('date range with minimum size within outer range', function() {
  beforeEach(resetOuterRange)

  it('range can be requested near the beginning of outer range', function() {
    var oldRange = createRange('03/28/2011', '04/03/2011')
    var newRange = DateRange.rangeWithMinimumSize(oldRange, 7, true, outerRange)
    expect(newRange.days()).toEqual(7)
    expect(newRange.start.getDate()).toEqual(29)
    expect(newRange.end.getDate()).toEqual(4)
  })

  it('range can be requested near the end of outer range', function() {
    var oldRange = createRange('04/25/2011', '04/25/2011')
    var newRange = DateRange.rangeWithMinimumSize(oldRange, 7, false, outerRange)
    expect(newRange.days()).toEqual(7)
  })

  it('range cannot be requested to be outside outer range', function() {
    var oldRange = createRange('04/26/2011', '04/26/2011')
    var newRange = DateRange.rangeWithMinimumSize(oldRange, 7, false, outerRange)
    expect(newRange.days()).toEqual(0)
  })

  it('range may not be found near the end of outer range due to weekends', function() {
    var oldRange = createRange('04/24/2011', '04/24/2011')
    var newRange = DateRange.rangeWithMinimumSize(oldRange, 7, true, outerRange)
    expect(newRange.days()).toEqual(0)
  })
})

describe('date range with time behavior', function() {
  beforeEach(resetRange)

  it('date range can have times', function() {
    DATE_LOCALE_EN.init()
    range.setTimes('10:00', '14:45')
    expect(range.days()).toEqual(2)
    expect(range.hours()).toEqual(4)
    expect(range.minutes()).toEqual(45)
    expect(range.toString()).toEqual('2 Days 4.75 Hours')

    DATE_LOCALE_FI.init()
    expect(range.toString()).toEqual('2 päivää 4,75 tuntia')

    range.setTimes('17:00', '16:00')
    expect(range.days()).toEqual(1)
    expect(range.hours()).toEqual(23)
    expect(range.minutes()).toEqual(0)
    range.start = range.start.plusDays(1)

    range.setTimes('10:00', '11:00')
    expect(range.toString()).toEqual('1 päivä 1 tunti')

    DATE_LOCALE_EN.init()
    expect(range.toString()).toEqual('1 Day 1 Hour')
  })

  it('one day range with start time after end time is not valid', function() {
    expect(range).toBeValidRange()
    range.start = new Date('09/13/2009')
    expect(range).not.toBeValidRange()
    range.start = new Date('09/12/2009')
    expect(range).toBeValidRange()
    range.setTimes('15:00', '14:30')
    expect(range).not.toBeValidRange()
    range.setTimes('15:00', '15:00')
    expect(range).toBeValidRange()
    range.setTimes('15:00', '15:30')
    expect(range).toBeValidRange()
  })

  it('invalid time will make range invalid while keeping date information', function() {
    range.setTimes('15:00', '15:30')
    expect(range).toBeValidRange()

    range.setTimes('', '15:30')
    expect(range).not.toBeValidRange()

    range.setTimes('15:00', '15:30')
    expect(range).toBeValidRange()

    range.setTimes('asdf', 'fddd')
    expect(range).not.toBeValidRange()

    range.setTimes('00', '25')
    expect(range).not.toBeValidRange()

  })

  it('different time formats are accepted', function() {
    range.setTimes('15:00', '16:10')
    assertHasCorrectHoursAndMinutes(1, 10)

    range.setTimes('14.00', '16.20')
    assertHasCorrectHoursAndMinutes(2, 20)

    range.setTimes('13,00', '16,30')
    assertHasCorrectHoursAndMinutes(3, 30)

    range.setTimes('1200', '1640')
    assertHasCorrectHoursAndMinutes(4, 40)

    range.setTimes('830', '1240')
    assertHasCorrectHoursAndMinutes(4, 10)

    range.setTimes('08', '13')
    assertHasCorrectHoursAndMinutes(5, 0)
  })

  it('minutes are rounded to 2 digits', function() {
    range.setTimes('15:00', '16:10')
    assertHasCorrectHoursAndMinutes(1, 10)
    DATE_LOCALE_FI.init()
    expect(range.toString()).toEqual('2 päivää 1,17 tuntia')
  })

  it('range is displayed with the most defining unit', function() {
    range = createRange('01/01/2004', '05/01/2006')
    expect(range).toPrintDefiningDurationOf('2 vuotta')
    range = createRange('01/01/2004', '05/01/2005')
    expect(range).toPrintDefiningDurationOf('1 vuosi')
    range = createRange('01/01/2004', '05/01/2004')
    expect(range).toPrintDefiningDurationOf('4 kuukautta')
    range = createRange('01/01/2004', '02/16/2004')
    expect(range).toPrintDefiningDurationOf('1 kuukausi')
    range = createRange('01/01/2004', '01/31/2004')
    expect(range).toPrintDefiningDurationOf('1 kuukausi')
    range = createRange('01/01/2004', '01/07/2004')
    expect(range).toPrintDefiningDurationOf('7 päivää')
  })
})

function assertHasCorrectHoursAndMinutes(hours, minutes) {
  expect(range).toBeValidRange()
  expect(range.hours()).toEqual(hours)
  expect(range.minutes()).toEqual(minutes)
}

function resetRange() {
  start = new Date('09/10/2009')
  end = new Date('09/12/2009')
  range = new DateRange(end, start)
}

function resetOuterRange() {
  start = new Date('03/28/2011')
  end = new Date('05/01/2011')
  outerRange = new DateRange(start, end)
}

function createRange(date1, date2) {
  return new DateRange(new Date(date1), new Date(date2))
}
<|endoftext|>


<commit_before>var google = require('googleapis');
var request = require('request');

var drive = google.drive('v2');
var OAuth2 = google.auth.OAuth2;
var oauth2Client = new OAuth2();

var fs = require('fs');
var token = fs.readFileSync(process.env.HOME + '/.gs-tamer','utf8')
oauth2Client.setCredentials({ access_token: token });
google.options({ auth: oauth2Client });

// map of google script type to file extensions:
var TYPES = {
  'server_js': '.gs',
  'html': '.html'
}

module.exports = {
  create: function(callback) {
    var opts = {
      url: 'https://www.googleapis.com/upload/drive/v2/files?convert=true',
      method: 'POST',
      headers: {
        'Authorization': 'Bearer ' + token,
        'Content-Type': 'application/vnd.google-apps.script+json'
      },
      body: JSON.stringify({
        files: [{
          "name":"Code",
          "type":"server_js",
          "source":"// created via gst"
        }]
      })
    };
    request(opts, function(err, res) {
      callback(err, JSON.parse(res.body));
    });
  },

  push: function(projectName, files, callback) {
    var self = this;
    this.pull(projectName, function(err, project) {
      var status = {}
      project.files.forEach(function(fileProject) {
        status[self.scriptFileName(fileProject)] = 'Deleted';
        files.forEach(function(fileLocal) {
          // append id to files that are already in the project
          // so they are updated
          if (fileProject.name == fileLocal.name) {
            fileLocal.id = fileProject.id
            status[self.scriptFileName(fileProject)] = 'Updated';
          }
          else {
            status[self.scriptFileName(fileProject)] = status[self.scriptFileName(fileProject)] || 'Created';
          }
        });
      });

      for (file in status) {
        console.log(status[file] + ' ' + file);
      }

      var opts = {
        url: 'https://www.googleapis.com/upload/drive/v2/files/' + project.id,
        method: 'PUT',
        headers: {
          'Authorization': 'Bearer ' + token,
          'Content-Type': 'application/vnd.google-apps.script+json'
        },
        body: JSON.stringify({ files: files })
      };
      request(opts, function(err, res) {
        callback(err, JSON.parse(res.body));
      });
    })
  },

  list: function(callback) {
    var params = {
      q: "mimeType='application/vnd.google-apps.script' and 'me' in owners"
    };

    drive.files.list(params, function(err, res) {
      if (err) {
        console.error(err);
      }
      callback(res.items);
    });
  },

  info: function(projectName, callback) {
    var params = {
      q: "title='" + projectName + "' and mimeType='application/vnd.google-apps.script' and 'me' in owners"
    };
    drive.files.list(params, function(err, res) {
      if (err) {
        console.error(err);
      }
      else if (res.items.length == 0) {
        console.error('Project not found: ' + projectName);
      }
      else {
        callback(err, res.items[0])
      }
    });
  },

  pull: function(projectName, callback) {
    this.info(projectName, function(err, project) {
      var url = project.exportLinks['application/vnd.google-apps.script+json'];
      var opts = {
        url: url,
        headers: { 'Authorization': 'Bearer ' + token }
      };
      request(opts, function(err, res) {
        var info = JSON.parse(res.body);
        info.id = project.id; // append project id to response
        callback(err, info)
      });
    });
  },

  scriptEditorUrl: function(project) {
    return 'https://script.google.com/d/' + project.id + '/edit?usp=drive_web'
  },

  scriptTypeForExtension: function(ext) {
    for (type in TYPES) {
      if (TYPES[type] == ext)
        return type;
    }
  },

  scriptExtensionForType: function(type) {
    return TYPES[type];
  },

  scriptFileName: function(file) {
    return file.name + this.scriptExtensionForType(file.type);
  }
}<commit_msg>oops, do not throw exception for auth on first use<commit_after>var google = require('googleapis');
var request = require('request');

var drive = google.drive('v2');
var OAuth2 = google.auth.OAuth2;
var oauth2Client = new OAuth2();

// cheap auth for now
try {
  var fs = require('fs');
  var token = fs.readFileSync(process.env.HOME + '/.gs-tamer','utf8')
  oauth2Client.setCredentials({ access_token: token });
  google.options({ auth: oauth2Client });
} catch(e) {

}

// map of google script type to file extensions:
var TYPES = {
  'server_js': '.gs',
  'html': '.html'
}

module.exports = {
  create: function(callback) {
    var opts = {
      url: 'https://www.googleapis.com/upload/drive/v2/files?convert=true',
      method: 'POST',
      headers: {
        'Authorization': 'Bearer ' + token,
        'Content-Type': 'application/vnd.google-apps.script+json'
      },
      body: JSON.stringify({
        files: [{
          "name":"Code",
          "type":"server_js",
          "source":"// created via gst"
        }]
      })
    };
    request(opts, function(err, res) {
      callback(err, JSON.parse(res.body));
    });
  },

  push: function(projectName, files, callback) {
    var self = this;
    this.pull(projectName, function(err, project) {
      var status = {}
      project.files.forEach(function(fileProject) {
        status[self.scriptFileName(fileProject)] = 'Deleted';
        files.forEach(function(fileLocal) {
          // append id to files that are already in the project
          // so they are updated
          if (fileProject.name == fileLocal.name) {
            fileLocal.id = fileProject.id
            status[self.scriptFileName(fileProject)] = 'Updated';
          }
          else {
            status[self.scriptFileName(fileProject)] = status[self.scriptFileName(fileProject)] || 'Created';
          }
        });
      });

      for (file in status) {
        console.log(status[file] + ' ' + file);
      }

      var opts = {
        url: 'https://www.googleapis.com/upload/drive/v2/files/' + project.id,
        method: 'PUT',
        headers: {
          'Authorization': 'Bearer ' + token,
          'Content-Type': 'application/vnd.google-apps.script+json'
        },
        body: JSON.stringify({ files: files })
      };
      request(opts, function(err, res) {
        callback(err, JSON.parse(res.body));
      });
    })
  },

  list: function(callback) {
    var params = {
      q: "mimeType='application/vnd.google-apps.script' and 'me' in owners"
    };

    drive.files.list(params, function(err, res) {
      if (err) {
        console.error(err);
      }
      callback(res.items);
    });
  },

  info: function(projectName, callback) {
    var params = {
      q: "title='" + projectName + "' and mimeType='application/vnd.google-apps.script' and 'me' in owners"
    };
    drive.files.list(params, function(err, res) {
      if (err) {
        console.error(err);
      }
      else if (res.items.length == 0) {
        console.error('Project not found: ' + projectName);
      }
      else {
        callback(err, res.items[0])
      }
    });
  },

  pull: function(projectName, callback) {
    this.info(projectName, function(err, project) {
      var url = project.exportLinks['application/vnd.google-apps.script+json'];
      var opts = {
        url: url,
        headers: { 'Authorization': 'Bearer ' + token }
      };
      request(opts, function(err, res) {
        var info = JSON.parse(res.body);
        info.id = project.id; // append project id to response
        callback(err, info)
      });
    });
  },

  scriptEditorUrl: function(project) {
    return 'https://script.google.com/d/' + project.id + '/edit?usp=drive_web'
  },

  scriptTypeForExtension: function(ext) {
    for (type in TYPES) {
      if (TYPES[type] == ext)
        return type;
    }
  },

  scriptExtensionForType: function(type) {
    return TYPES[type];
  },

  scriptFileName: function(file) {
    return file.name + this.scriptExtensionForType(file.type);
  }
}<|endoftext|>


<commit_before>'use strict';
let asyncEachLimit = require('async/eachLimit');

/** http://docs.datastax.com/en/developer/nodejs-driver/3.2/features/batch/
 * TODO: ASYNC_BATCH_LIMIT will need to be tuned.
 * It is best to keep the batch size small (in the order of 10s).
 * If the batch size is greater than 5k, then the server will issue a warning.
 */
const ASYNC_BATCH_LIMIT = 10;

module.exports = {

  /** Used to execute multiple INSERT, UPDATE, or DELETE statements
   * @param Client client - The cassandra client
   * @param Array<{query, params}> mutations - cassandra prepared statements
   */
  batchMutations: (client, mutations, callback) => {
    asyncEachLimit(mutations, ASYNC_BATCH_LIMIT, (mutationBatch, asyncCB) => processMutation(client, mutationBatch, asyncCB),
      finalCBErr => {
        if(finalCBErr){
          console.error(`Error occured during the batch: ${JSON.stringify(finalCBErr)}`);
        }else{
          console.log('Finished executing cassandra prepared statements');
        }
        callback(finalCBErr);
      }
    );
  }

};

let processMutation = (client, mutationBatch, asyncCB) => {
  client.batch(mutationBatch, { prepare: true }, function(err) {
    if(err) {
      asyncCB('Failed to process mutation batch');
    }
  });
};<commit_msg>Fix comment<commit_after>'use strict';
let asyncEachLimit = require('async/eachLimit');

/** http://docs.datastax.com/en/developer/nodejs-driver/3.2/features/batch/
 * TODO: ASYNC_BATCH_LIMIT will need to be tuned.
 * It is best to keep the batch size small (in the order of 10s).
 * If the batch size is greater than 5k, then the server will issue a warning.
 */
const ASYNC_BATCH_LIMIT = 10;

module.exports = {

  /** Used to execute multiple INSERT, UPDATE, or DELETE statements
   * @param Client client - The cassandra client
   * @param Array<{mutation, params}> mutations - cassandra prepared statements
   */
  batchMutations: (client, mutations, callback) => {
    asyncEachLimit(mutations, ASYNC_BATCH_LIMIT, (mutationBatch, asyncCB) => processMutation(client, mutationBatch, asyncCB),
      finalCBErr => {
        if(finalCBErr){
          console.error(`Error occured during the batch: ${JSON.stringify(finalCBErr)}`);
        }else{
          console.log('Finished executing cassandra prepared statements');
        }
        callback(finalCBErr);
      }
    );
  }

};

let processMutation = (client, mutationBatch, asyncCB) => {
  client.batch(mutationBatch, { prepare: true }, function(err) {
    if(err) {
      asyncCB('Failed to process mutation batch');
    }
  });
};<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var archiver = require('archiver');
var AWS = require('aws-sdk');
var _ = require('lodash');
var minimist = require('minimist');

var argv = minimist(process.argv.slice(2));

if (argv._[0] === 'init') {
  var template = {
    aws: {
      accessKeyId: '',
      secretAccessKey: '',
      region: ''
    },
    bucket: '',
    cwd: '',
    erase: false,
    files: []
  };
  fs.writeFileSync('task.json', JSON.stringify(template, null, 2));
  process.exit();
}

function usage() {
  console.log('Usage');
  console.log('gspark -c [config file]');
  process.exit();
}

if (!argv.c) {
  usage();
}
var config = JSON.parse(fs.readFileSync(argv.c));
var files = [];

if (argv.f) {
  files = argv.f.split(',')
} else {
  if (config.files.length === 0) {
    console.log('No files specified, exiting...');
    process.exit();
  } else {
    files = config.files;
  }
}

AWS.config.update(config.aws);
var s3Stream = require('s3-upload-stream')(new AWS.S3());

var archive = archiver('zip');
var i, file, filePath, name, stream;
var _files = [];
for (i = 0;i < files.length;i++) {
  file = files[i];
  name = path.basename(file);
  filePath = path.join(config.cwd, file);
  _files.push(filePath);
  stream = fs.createReadStream(filePath);
  archive.append(stream, {
    name: name
  });
}

var fileKey = config.key;
if (!fileKey) {
  fileKey = String(Date.now()) + '.zip';
}

var upload = s3Stream.upload({
  Bucket: config.bucket,
  Key: fileKey,
  ServerSideEncryption: 'AES256',
  ACL: 'private'
});

upload.on('error', function(err) {
  console.log(err);
  console.log(err.stack);
  process.exit();
});

upload.on('uploaded', function(result) {
  console.log('Upload completed!');
  if (config.erase) {
    console.log('Erasing files...');
    for (i in _files) {
      fs.unlinkSync(files[i]);
    }
    console.log('Files erased!');
  }
  process.exit();
});

archive.on('error', function(err) {
  console.log(err);
  console.log(err.stack);
  process.exit();
});

archive.pipe(upload);
archive.finalize();
<commit_msg>Fix file paths<commit_after>var fs = require('fs');
var path = require('path');
var archiver = require('archiver');
var AWS = require('aws-sdk');
var _ = require('lodash');
var minimist = require('minimist');

var argv = minimist(process.argv.slice(2));

if (argv._[0] === 'init') {
  var template = {
    aws: {
      accessKeyId: '',
      secretAccessKey: '',
      region: ''
    },
    bucket: '',
    cwd: '',
    erase: false,
    files: []
  };
  fs.writeFileSync('task.json', JSON.stringify(template, null, 2));
  process.exit();
}

function usage() {
  console.log('Usage');
  console.log('gspark -c [config file]');
  process.exit();
}

if (!argv.c) {
  usage();
}
var config = JSON.parse(fs.readFileSync(argv.c));
var files = [];

if (argv.f) {
  files = argv.f.split(',')
} else {
  if (config.files.length === 0) {
    console.log('No files specified, exiting...');
    process.exit();
  } else {
    files = _.map(config.files, function(file) {
      return path.join(config.cwd, file);
    });
  }
}

AWS.config.update(config.aws);
var s3Stream = require('s3-upload-stream')(new AWS.S3());

var archive = archiver('zip');
var i, file, filePath, name, stream;
for (i = 0;i < files.length;i++) {
  file = files[i];
  name = path.basename(file);
  stream = fs.createReadStream(file);
  archive.append(stream, {
    name: name
  });
}

var archiveKey = config.key;
if (!archiveKey) {
  archiveKey = String(Date.now()) + '.zip';
}

var upload = s3Stream.upload({
  Bucket: config.bucket,
  Key: archiveKey,
  ServerSideEncryption: 'AES256',
  ACL: 'private'
});

upload.on('error', function(err) {
  console.log(err);
  console.log(err.stack);
  process.exit();
});

upload.on('uploaded', function(result) {
  console.log('Upload completed!');
  if (config.erase) {
    console.log('Erasing files...');
    for (i in files) {
      fs.unlinkSync(files[i]);
    }
    console.log('Files erased!');
  }
  process.exit();
});

archive.on('error', function(err) {
  console.log(err);
  console.log(err.stack);
  process.exit();
});

archive.pipe(upload);
archive.finalize();
<|endoftext|>


<commit_before>'use strict';
var React = require('react');
var _ = require('underscore');
var url = require('url');
var moment = require('moment');
var globals = require('./globals');
var curator = require('./curator');
var modal = require('../libs/bootstrap/modal');
var form = require('../libs/bootstrap/form');
var parseAndLogError = require('./mixins').parseAndLogError;
var RestMixin = require('./rest').RestMixin;
var CurationMixin = require('./curator').CurationMixin;
var parsePubmed = require('../libs/parse-pubmed').parsePubmed;

var Modal = modal.Modal;
var ModalMixin = modal.ModalMixin;
var Form = form.Form;
var FormMixin = form.FormMixin;
var Input = form.Input;
var PmidDoiButtons = curator.PmidDoiButtons;
var RecordHeader = curator.RecordHeader;
var CurationPalette = curator.CurationPalette;
var PmidSummary = curator.PmidSummary;
var queryKeyValue = globals.queryKeyValue;
var external_url_map = globals.external_url_map;
var userMatch = globals.userMatch;

// Curator page content
var CurationCentral = React.createClass({
    mixins: [RestMixin, CurationMixin],

    getInitialState: function() {
        return {
            currPmid: queryKeyValue('pmid', this.props.href),
            currGdm: {}
        };
    },

    // Called when currently selected PMID changes
    currPmidChange: function(pmid) {
        if (pmid !== undefined) {
            var gdm = this.state.currGdm;

            if (Object.keys(gdm).length) {
                // Find the annotation in the GDM matching the given pmid
                var currAnnotation = _(gdm.annotations).find(annotation => {
                    return annotation.article.pmid === pmid;
                });

                if (currAnnotation) {
                    this.setState({currPmid: currAnnotation.article.pmid});
                }
            }
            if (this.state.currGdm && Object.keys(this.state.currGdm).length) {
                window.history.replaceState(window.state, '', '/curation-central/?gdm=' + this.state.currGdm.uuid + '&pmid=' + pmid);
            }
        }
    },

    // Retrieve the GDM object from the DB with the given uuid
    getGdm: function(uuid, pmid) {
        this.getRestData('/gdm/' + uuid).then(gdm => {
            // The GDM object successfully retrieved; set the Curator Central component
            this.setState({currGdm: gdm, currOmimId: gdm.omimId});
            this.currPmidChange(pmid);
        }).catch(parseAndLogError.bind(undefined, 'putRequest'));
    },

    // After the Curator Central page component mounts, grab the uuid from the query string and
    // retrieve the corresponding GDM from the DB.
    componentDidMount: function() {
        var gdmUuid = queryKeyValue('gdm', this.props.href);
        var pmid = queryKeyValue('pmid', this.props.href);
        if (gdmUuid) {
            this.getGdm(gdmUuid, pmid);
        }
    },

    // Add an article whose object is given to the current GDM
    updateGdmArticles: function(article) {
        var newAnnotation;
        var currGdm = this.state.currGdm;

        // Put together a new annotation object with the article reference
        var newAnnotationObj = {
            article: article.pmid,
            active: true
        };

        // Post new annotation to the DB. fetch returns a JS promise.
        this.postRestData('/evidence/', newAnnotationObj).then(data => {
            // Save the new annotation; fetch the currently displayed GDM as an object without its embedded
            // objects; basically the object as it exists in the DB. We'll update that and write it back to the DB.
            return (data['@graph'][0]);
        }).then(newAnnotation => {
            var gdmObj = curator.flatten(currGdm);

            // Add our new annotation reference to the array of annotations in the GDM.
            if (!gdmObj.annotations) {
                gdmObj.annotations = [];
            }
            gdmObj.annotations.push(newAnnotation['@id']);
            return this.putRestData('/gdm/' + currGdm.uuid, gdmObj);
        }).then(data => {
            // Retrieve the updated GDM and set it as the new state GDM to force a rerendering.
            this.getGdm(data['@graph'][0].uuid, article.pmid);
        }).catch(parseAndLogError.bind(undefined, 'putRequest'));
    },

    render: function() {
        var gdm = this.state.currGdm;
        var pmid = this.state.currPmid;

        // Find the GDM's annotation for the article with the curren PMID
        var annotation = gdm.annotations && gdm.annotations.length && _(gdm.annotations).find(function(annotation) {
            return pmid === annotation.article.pmid;
        });
        var currArticle = annotation ? annotation.article : null;

        return (

                <RecordHeader gdm={gdm} omimId={this.state.currOmimId} updateOmimId={this.updateOmimId} />

                            <PmidSelectionList annotations={gdm.annotations} currPmid={pmid} currPmidChange={this.currPmidChange}
                                    protocol={this.props.href_url.protocol} updateGdmArticles={this.updateGdmArticles} currGdm={gdm} />

                            {currArticle ?

                                    <PmidSummary article={currArticle} displayJournal />
                                    <PmidDoiButtons pmid={currArticle.pmid} />
                                    <BetaNote annotation={annotation} session={this.props.session} />
                                    {currArticle.abstract ?

Abstract

{currArticle.abstract}

                                    : null}

                            : null}

                        {currArticle ?

                                <CurationPalette gdm={gdm} annotation={annotation} session={this.props.session} />

                        : null}

        );
    }
});

globals.curator_page.register(CurationCentral, 'curator_page', 'curation-central');

var BetaNote = React.createClass({
    render: function() {
        var annotation = this.props.annotation;
        var session = this.props.session;
        var curatorMatch = annotation && userMatch(annotation.submitted_by, session);

        return (

                {!curatorMatch ?

Currently, only the curator who adds a paper to a Gene-Disease record can associate evidence with that paper.

PMID:{annotation.article.pmid} added by {annotation.submitted_by.title}.

                : null}

        );
    }
});

// Display the list of PubMed articles passed in pmidItems.
var PmidSelectionList = React.createClass({
    mixins: [ModalMixin],

    propTypes: {
        annotations: React.PropTypes.array, // List of PubMed items
        protocol: React.PropTypes.string, // Protocol to use to access PubMed ('http:' or 'https:')
        currPmid: React.PropTypes.string, // PMID of currently selected article
        currPmidChange: React.PropTypes.func, // Function to call when currently selected article changes
        updateGdmArticles: React.PropTypes.func // Function to call when we have an article to add to the GDM
    },

    render: function() {
        var annotations = _(this.props.annotations).sortBy(function(annotation) {
            // Sort list of articles by first author
            return annotation.article.authors[0];
        });

        return (

                    <Modal title='Add new PubMed Article'>
                        <button className="btn btn-primary pmid-selection-add-btn" modal={<AddPmidModal protocol={this.props.protocol} closeModal={this.closeModal} updateGdmArticles={this.props.updateGdmArticles} currGdm={this.props.currGdm} />}>
                            Add New PMID(s)
                        </button>
                    </Modal>

                {annotations ?

                        {annotations.map(annotation => {
                            var classList = 'pmid-selection-list-item' + (annotation.article.pmid === this.props.currPmid ? ' curr-pmid' : '');

                            return (

                                        <PmidSummary article={annotation.article} />

PMID: {annotation.article.pmid}

                            );
                        })}

                : null}
                {annotations.length === 0 ?

                        Add papers to this Gene-Disease Record using the Add New PMID(s) button; click on any added paper to view its abstract and begin curating evidence from that paper.

                :null }

        );
    }
});

// The content of the Add PMID(s) modal dialog box
var AddPmidModal = React.createClass({
    mixins: [FormMixin, RestMixin],

    propTypes: {
        closeModal: React.PropTypes.func, // Function to call to close the modal
        protocol: React.PropTypes.string, // Protocol to use to access PubMed ('http:' or 'https:')
        updateGdmArticles: React.PropTypes.func // Function to call when we have an article to add to the GDM
    },

    contextTypes: {
        fetch: React.PropTypes.func // Function to perform a search
    },

    // Form content validation
    validateForm: function() {
        // Start with default validation
        var valid = this.validateDefault();
        var rawInput = this.getFormValue('pmid');
        var strippedInput = this.getFormValue('pmid').replace(/^[0]+/g,""); // strip leading 0s

        if (valid) {
            // valid if input isn't zero-filled
            valid = strippedInput.length > 0;
            if (!valid) this.setFormErrors('pmid', 'This PMID does not exist');
            else {
                // valid if input isn't zero-leading (but not zero-filled)
                valid = rawInput.length == strippedInput.length;
                if (!valid) this.setFormErrors('pmid', 'Did you mean the PMID "' + strippedInput + '"? Please remove any leading 0\'s from your PMID');
                else {
                    // valid if the input has only numbers
                    valid = rawInput.match(/^[0-9]*$/i);
                    if (!valid) this.setFormErrors('pmid', 'Only numbers allowed');
                    else {
                        // valid if the input is at most 8 numbers long
                        valid = rawInput.length < 9;
                        if (!valid) this.setFormErrors('pmid', 'This PMID does not exist');
                        else {
                            // valid if input isn't already associated with GDM
                            for (var i = 0; i < this.props.currGdm.annotations.length; i++) {
                                if (this.props.currGdm.annotations[i].article.pmid == rawInput) {
                                    valid = false;
                                    this.setFormErrors('pmid', 'This article has already been associated with this GDM');
                                }
                            }
                        }
                    }
                }
            }
        }
        return valid;
    },

    // Called when the modal form’s submit button is clicked. Handles validation and triggering
    // the process to add an article.
    submitForm: function(e) {
        e.preventDefault(); e.stopPropagation(); // Don't run through HTML submit handler
        this.saveFormValue('pmid', this.refs.pmid.getValue());
        if (this.validateForm()) {
            // Form is valid -- we have a good PMID. Fetch the article with that PMID
            var enteredPmid = this.getFormValue('pmid');
            this.getRestData('/articles/' + enteredPmid).then(article => {
                // Close the modal; update the GDM with this article.
                return Promise.resolve(article);
            }, e => {
                var url = this.props.protocol + external_url_map['PubMedSearch'];
                // PubMed article not in our DB; go out to PubMed itself to retrieve it as XML
                return this.getRestDataXml(external_url_map['PubMedSearch'] + enteredPmid).then(xml => {
                    var newArticle = parsePubmed(xml, enteredPmid);
                    // if the PubMed article for this PMID doesn't exist, display an error
                    if (newArticle.length == undefined) this.setFormErrors('pmid', 'This PMID does not exist');
                    return this.postRestData('/articles/', newArticle).then(data => {
                        return Promise.resolve(data['@graph'][0]);
                    });
                });
            }).then(article => {
                this.props.closeModal();
                this.props.updateGdmArticles(article);
            }).catch(function(e) {
                console.log('ERROR %o', e);
            });
        }
    },

    // Called when the modal form's cancel button is clicked. Just closes the modal like
    // nothing happened.
    cancelForm: function(e) {
        e.preventDefault(); e.stopPropagation(); // Don't run through HTML submit handler
        this.props.closeModal();
    },

    render: function() {
        return (
            <Form submitHandler={this.submitForm} formClassName="form-std">

                    <Input type="text" ref="pmid" label="Enter a PubMed ID"
                        error={this.getFormError('pmid')} clearError={this.clrFormErrors.bind(null, 'pmid')}
                        labelClassName="control-label" groupClassName="form-group" required />

                    <Input type="cancel" inputClassName="btn-default btn-inline-spacer" cancelHandler={this.cancelForm} />
                    <Input type="submit" inputClassName={this.getFormError('pmid') === null || this.getFormError('pmid') === undefined || this.getFormError('pmid') === '' ?
                        "btn-primary btn-inline-spacer" : "btn-primary btn-inline-spacer disabled"} title="Add Article" />

            </Form>
        );
    }
});
<commit_msg>update wording of error messages<commit_after>'use strict';
var React = require('react');
var _ = require('underscore');
var url = require('url');
var moment = require('moment');
var globals = require('./globals');
var curator = require('./curator');
var modal = require('../libs/bootstrap/modal');
var form = require('../libs/bootstrap/form');
var parseAndLogError = require('./mixins').parseAndLogError;
var RestMixin = require('./rest').RestMixin;
var CurationMixin = require('./curator').CurationMixin;
var parsePubmed = require('../libs/parse-pubmed').parsePubmed;

var Modal = modal.Modal;
var ModalMixin = modal.ModalMixin;
var Form = form.Form;
var FormMixin = form.FormMixin;
var Input = form.Input;
var PmidDoiButtons = curator.PmidDoiButtons;
var RecordHeader = curator.RecordHeader;
var CurationPalette = curator.CurationPalette;
var PmidSummary = curator.PmidSummary;
var queryKeyValue = globals.queryKeyValue;
var external_url_map = globals.external_url_map;
var userMatch = globals.userMatch;

// Curator page content
var CurationCentral = React.createClass({
    mixins: [RestMixin, CurationMixin],

    getInitialState: function() {
        return {
            currPmid: queryKeyValue('pmid', this.props.href),
            currGdm: {}
        };
    },

    // Called when currently selected PMID changes
    currPmidChange: function(pmid) {
        if (pmid !== undefined) {
            var gdm = this.state.currGdm;

            if (Object.keys(gdm).length) {
                // Find the annotation in the GDM matching the given pmid
                var currAnnotation = _(gdm.annotations).find(annotation => {
                    return annotation.article.pmid === pmid;
                });

                if (currAnnotation) {
                    this.setState({currPmid: currAnnotation.article.pmid});
                }
            }
            if (this.state.currGdm && Object.keys(this.state.currGdm).length) {
                window.history.replaceState(window.state, '', '/curation-central/?gdm=' + this.state.currGdm.uuid + '&pmid=' + pmid);
            }
        }
    },

    // Retrieve the GDM object from the DB with the given uuid
    getGdm: function(uuid, pmid) {
        this.getRestData('/gdm/' + uuid).then(gdm => {
            // The GDM object successfully retrieved; set the Curator Central component
            this.setState({currGdm: gdm, currOmimId: gdm.omimId});
            this.currPmidChange(pmid);
        }).catch(parseAndLogError.bind(undefined, 'putRequest'));
    },

    // After the Curator Central page component mounts, grab the uuid from the query string and
    // retrieve the corresponding GDM from the DB.
    componentDidMount: function() {
        var gdmUuid = queryKeyValue('gdm', this.props.href);
        var pmid = queryKeyValue('pmid', this.props.href);
        if (gdmUuid) {
            this.getGdm(gdmUuid, pmid);
        }
    },

    // Add an article whose object is given to the current GDM
    updateGdmArticles: function(article) {
        var newAnnotation;
        var currGdm = this.state.currGdm;

        // Put together a new annotation object with the article reference
        var newAnnotationObj = {
            article: article.pmid,
            active: true
        };

        // Post new annotation to the DB. fetch returns a JS promise.
        this.postRestData('/evidence/', newAnnotationObj).then(data => {
            // Save the new annotation; fetch the currently displayed GDM as an object without its embedded
            // objects; basically the object as it exists in the DB. We'll update that and write it back to the DB.
            return (data['@graph'][0]);
        }).then(newAnnotation => {
            var gdmObj = curator.flatten(currGdm);

            // Add our new annotation reference to the array of annotations in the GDM.
            if (!gdmObj.annotations) {
                gdmObj.annotations = [];
            }
            gdmObj.annotations.push(newAnnotation['@id']);
            return this.putRestData('/gdm/' + currGdm.uuid, gdmObj);
        }).then(data => {
            // Retrieve the updated GDM and set it as the new state GDM to force a rerendering.
            this.getGdm(data['@graph'][0].uuid, article.pmid);
        }).catch(parseAndLogError.bind(undefined, 'putRequest'));
    },

    render: function() {
        var gdm = this.state.currGdm;
        var pmid = this.state.currPmid;

        // Find the GDM's annotation for the article with the curren PMID
        var annotation = gdm.annotations && gdm.annotations.length && _(gdm.annotations).find(function(annotation) {
            return pmid === annotation.article.pmid;
        });
        var currArticle = annotation ? annotation.article : null;

        return (

                <RecordHeader gdm={gdm} omimId={this.state.currOmimId} updateOmimId={this.updateOmimId} />

                            <PmidSelectionList annotations={gdm.annotations} currPmid={pmid} currPmidChange={this.currPmidChange}
                                    protocol={this.props.href_url.protocol} updateGdmArticles={this.updateGdmArticles} currGdm={gdm} />

                            {currArticle ?

                                    <PmidSummary article={currArticle} displayJournal />
                                    <PmidDoiButtons pmid={currArticle.pmid} />
                                    <BetaNote annotation={annotation} session={this.props.session} />
                                    {currArticle.abstract ?

Abstract

{currArticle.abstract}

                                    : null}

                            : null}

                        {currArticle ?

                                <CurationPalette gdm={gdm} annotation={annotation} session={this.props.session} />

                        : null}

        );
    }
});

globals.curator_page.register(CurationCentral, 'curator_page', 'curation-central');

var BetaNote = React.createClass({
    render: function() {
        var annotation = this.props.annotation;
        var session = this.props.session;
        var curatorMatch = annotation && userMatch(annotation.submitted_by, session);

        return (

                {!curatorMatch ?

Currently, only the curator who adds a paper to a Gene-Disease record can associate evidence with that paper.

PMID:{annotation.article.pmid} added by {annotation.submitted_by.title}.

                : null}

        );
    }
});

// Display the list of PubMed articles passed in pmidItems.
var PmidSelectionList = React.createClass({
    mixins: [ModalMixin],

    propTypes: {
        annotations: React.PropTypes.array, // List of PubMed items
        protocol: React.PropTypes.string, // Protocol to use to access PubMed ('http:' or 'https:')
        currPmid: React.PropTypes.string, // PMID of currently selected article
        currPmidChange: React.PropTypes.func, // Function to call when currently selected article changes
        updateGdmArticles: React.PropTypes.func // Function to call when we have an article to add to the GDM
    },

    render: function() {
        var annotations = _(this.props.annotations).sortBy(function(annotation) {
            // Sort list of articles by first author
            return annotation.article.authors[0];
        });

        return (

                    <Modal title='Add new PubMed Article'>
                        <button className="btn btn-primary pmid-selection-add-btn" modal={<AddPmidModal protocol={this.props.protocol} closeModal={this.closeModal} updateGdmArticles={this.props.updateGdmArticles} currGdm={this.props.currGdm} />}>
                            Add New PMID(s)
                        </button>
                    </Modal>

                {annotations ?

                        {annotations.map(annotation => {
                            var classList = 'pmid-selection-list-item' + (annotation.article.pmid === this.props.currPmid ? ' curr-pmid' : '');

                            return (

                                        <PmidSummary article={annotation.article} />

PMID: {annotation.article.pmid}

                            );
                        })}

                : null}
                {annotations.length === 0 ?

                        Add papers to this Gene-Disease Record using the Add New PMID(s) button; click on any added paper to view its abstract and begin curating evidence from that paper.

                :null }

        );
    }
});

// The content of the Add PMID(s) modal dialog box
var AddPmidModal = React.createClass({
    mixins: [FormMixin, RestMixin],

    propTypes: {
        closeModal: React.PropTypes.func, // Function to call to close the modal
        protocol: React.PropTypes.string, // Protocol to use to access PubMed ('http:' or 'https:')
        updateGdmArticles: React.PropTypes.func // Function to call when we have an article to add to the GDM
    },

    contextTypes: {
        fetch: React.PropTypes.func // Function to perform a search
    },

    // Form content validation
    validateForm: function() {
        // Start with default validation
        var valid = this.validateDefault();
        var rawInput = this.getFormValue('pmid');
        var strippedInput = this.getFormValue('pmid').replace(/^[0]+/g,""); // strip leading 0s

        if (valid) {
            // valid if input isn't zero-filled
            valid = strippedInput.length > 0;
            if (!valid) this.setFormErrors('pmid', 'This PMID does not exist');
            else {
                // valid if input isn't zero-leading (but not zero-filled)
                valid = rawInput.length == strippedInput.length;
                if (!valid) this.setFormErrors('pmid', 'Please re-enter PubMed ID without any leading 0\'s');
                else {
                    // valid if the input has only numbers
                    valid = rawInput.match(/^[0-9]*$/i);
                    if (!valid) this.setFormErrors('pmid', 'Only numbers allowed');
                    else {
                        // valid if the input is at most 8 numbers long
                        valid = rawInput.length < 9;
                        if (!valid) this.setFormErrors('pmid', 'This PMID does not exist');
                        else {
                            // valid if input isn't already associated with GDM
                            for (var i = 0; i < this.props.currGdm.annotations.length; i++) {
                                if (this.props.currGdm.annotations[i].article.pmid == rawInput) {
                                    valid = false;
                                    this.setFormErrors('pmid', 'This article has already been associated with this Gene-Disease Record');
                                }
                            }
                        }
                    }
                }
            }
        }
        return valid;
    },

    // Called when the modal form’s submit button is clicked. Handles validation and triggering
    // the process to add an article.
    submitForm: function(e) {
        e.preventDefault(); e.stopPropagation(); // Don't run through HTML submit handler
        this.saveFormValue('pmid', this.refs.pmid.getValue());
        if (this.validateForm()) {
            // Form is valid -- we have a good PMID. Fetch the article with that PMID
            var enteredPmid = this.getFormValue('pmid');
            this.getRestData('/articles/' + enteredPmid).then(article => {
                // Close the modal; update the GDM with this article.
                return Promise.resolve(article);
            }, e => {
                var url = this.props.protocol + external_url_map['PubMedSearch'];
                // PubMed article not in our DB; go out to PubMed itself to retrieve it as XML
                return this.getRestDataXml(external_url_map['PubMedSearch'] + enteredPmid).then(xml => {
                    var newArticle = parsePubmed(xml, enteredPmid);
                    // if the PubMed article for this PMID doesn't exist, display an error
                    if (newArticle.length == undefined) this.setFormErrors('pmid', 'This PMID does not exist');
                    return this.postRestData('/articles/', newArticle).then(data => {
                        return Promise.resolve(data['@graph'][0]);
                    });
                });
            }).then(article => {
                this.props.closeModal();
                this.props.updateGdmArticles(article);
            }).catch(function(e) {
                console.log('ERROR %o', e);
            });
        }
    },

    // Called when the modal form's cancel button is clicked. Just closes the modal like
    // nothing happened.
    cancelForm: function(e) {
        e.preventDefault(); e.stopPropagation(); // Don't run through HTML submit handler
        this.props.closeModal();
    },

    render: function() {
        return (
            <Form submitHandler={this.submitForm} formClassName="form-std">

                    <Input type="text" ref="pmid" label="Enter a PubMed ID"
                        error={this.getFormError('pmid')} clearError={this.clrFormErrors.bind(null, 'pmid')}
                        labelClassName="control-label" groupClassName="form-group" required />

                    <Input type="cancel" inputClassName="btn-default btn-inline-spacer" cancelHandler={this.cancelForm} />
                    <Input type="submit" inputClassName={this.getFormError('pmid') === null || this.getFormError('pmid') === undefined || this.getFormError('pmid') === '' ?
                        "btn-primary btn-inline-spacer" : "btn-primary btn-inline-spacer disabled"} title="Add Article" />

            </Form>
        );
    }
});
<|endoftext|>


<commit_before>// 设置项目属性
fis.set('project.name', 'fis3-base');
fis.set('project.static', '/static');
fis.set('project.files', ['*.html', 'map.json', '/test/*']);

// 引入模块化开发插件，设置规范为 commonJs 规范。

fis.hook('commonjs', {
    baseUrl: './modules',
    extList: ['.js', '.es']
});

/*************************目录规范*****************************/

// 开启同名依赖
fis.match('/modules/**', {
    useSameNameRequire: true
});

// ------ 配置lib
fis.match('/lib/**.js', {
    release: '${project.static}/$&'
});

// ------ 配置components
fis.match('/components/**', {
    release: '${project.static}/$&'
});
fis.match('/components/**.css', {
    isMod: true,
    release: '${project.static}/$&'
});
fis.match('/components/**.js', {
    isMod: true,
    release: '${project.static}/$&'
});

// ------ 配置modules
fis.match('/modules/(**)', {
    release: '${project.static}/$1'
})

// 配置css
fis.match(/^\/modules\/(.*\.scss)$/i, {
    rExt: '.css',
    isMod: true,
    release: '${project.static}/$1',
    parser: fis.plugin('sass', {
        include_paths: ['modules/css', 'components'] // 加入文件查找目录
    }),
    postprocessor: fis.plugin('autoprefixer', {
        browsers: ['> 1% in CN', "last 2 versions", "IE >= 8"] // pc
        // browsers: ["Android >= 4", "ChromeAndroid > 1%", "iOS >= 6"] // wap
    })
});
fis.match(/^\/modules\/(.*\.css)$/i, {
    isMod: true,
    release: '${project.static}/$1',
    postprocessor: fis.plugin('autoprefixer', {
        browsers: ['> 1% in CN', "last 2 versions", "IE >= 8"] // pc
        // browsers: ["Android >= 4", "ChromeAndroid > 1%", "iOS >= 6"] // wap
    })
})
fis.match(/^\/modules\/(.*\.(?:png|jpg|gif))$/i, {
    release: '${project.static}/$1'
});

// 配置js
fis.match(/^\/modules\/(.*\.es)$/i, {
    parser: fis.plugin('babel-5.x'),
    rExt: 'js',
    isMod: true,
    release: '${project.static}/$1'
});
fis.match(/^\/modules\/(.*\.js)$/i, {
    isMod: true,
    release: '${project.static}/$1'
});

// ------ 配置前端模版 使用template.js
fis.match('**.tmpl', {
    parser: fis.plugin('template', {
        sTag: '<#',
        eTag: '#>',
        global: 'template'
    }),
    isJsLike: true,
    release : false
});

// ------ 配置模拟数据
fis.match('/test/**', {
  release: '$0'
});
fis.match('/test/server.conf', {
  release: '/config/server.conf'
});

/*************************打包规范*****************************/

// 因为是纯前端项目，依赖不能自断被加载进来，所以这里需要借助一个 loader 来完成，
// 注意：与后端结合的项目不需要此插件!!!
fis.match('::package', {
    // npm install [-g] fis3-postpackager-loader
    // 分析 __RESOURCE_MAP__ 结构，来解决资源加载问题
    postpackager: fis.plugin('loader', {
        resourceType: 'commonJs',
        useInlineMap: true // 资源映射表内嵌
    })
});

// 公用js
var map = {
    'prd-debug': {
        host: '',
        path: ''
    },
    'prd': {
        host: 'http://yanhaijing.com',
        path: '/${project.name}'
    }
};

fis.util.map(map, function (k, v) {
    var domain = v.host + v.path;

    fis.media(k)
        .match('**.{es,js}', {
            useHash: true,
            domain: domain
        })
        .match('**.{scss,css}', {
            useSprite: true,
            useHash: true,
            domain: domain
        })
        .match('::image', {
            useHash: true,
            domain: domain
        })
        .match('**/(*_{x,y,z}.png)', {
            release: '/pkg/$1'
        })
        // 启用打包插件，必须匹配 ::package
        .match('::package', {
            spriter: fis.plugin('csssprites', {
                layout: 'matrix',
                // scale: 0.5, // 移动端二倍图用
                margin: '10'
            }),
            postpackager: fis.plugin('loader', {
                allInOne: true,
            })
        })
        .match('/lib/es5-{shim,sham}.js', {
            packTo: '/pkg/es5-shim.js'
        })
        .match('/components/**.css', {
            packTo: '/pkg/components.css'
        })
        .match('/components/**.js', {
            packTo: '/pkg/components.js'
        })
        .match('/modules/**.{scss,css}', {
            packTo: '/pkg/modules.css'
        })
        .match('/modules/css/**.{scss,css}', {
            packTo: ''
        })
        .match('/modules/css/common.scss', {
            packTo: '/pkg/common.css'
        })
        .match('/modules/**.{es,js}', {
            packTo: '/pkg/modules.js'
        })
        .match('/modules/app/**.{es,js}', {
            packTo: '/pkg/aio.js'
        })
});

// 发布产品库
fis.media('prd')
    .match('**.{es,js}', {
        optimizer: fis.plugin('uglify-js')
    })
    .match('**.{scss,css}', {
        optimizer: fis.plugin('clean-css', {
            'keepBreaks': true //保持一个规则一个换行
        })
    });
<commit_msg>feat: 更改autoprefix配置<commit_after>// 设置项目属性
fis.set('project.name', 'fis3-base');
fis.set('project.static', '/static');
fis.set('project.files', ['*.html', 'map.json', '/test/*']);

// 引入模块化开发插件，设置规范为 commonJs 规范。

fis.hook('commonjs', {
    baseUrl: './modules',
    extList: ['.js', '.es']
});

/*************************目录规范*****************************/

// 开启同名依赖
fis.match('/modules/**', {
    useSameNameRequire: true
});

// ------ 配置lib
fis.match('/lib/**.js', {
    release: '${project.static}/$&'
});

// ------ 配置components
fis.match('/components/**', {
    release: '${project.static}/$&'
});
fis.match('/components/**.css', {
    isMod: true,
    release: '${project.static}/$&'
});
fis.match('/components/**.js', {
    isMod: true,
    release: '${project.static}/$&'
});

// ------ 配置modules
fis.match('/modules/(**)', {
    release: '${project.static}/$1'
})

// 配置css
fis.match(/^\/modules\/(.*\.scss)$/i, {
    rExt: '.css',
    isMod: true,
    release: '${project.static}/$1',
    parser: fis.plugin('sass', {
        include_paths: ['modules/css', 'components'] // 加入文件查找目录
    }),
    postprocessor: fis.plugin('autoprefixer', {
        browsers: ['> 1% in CN', 'last 2 versions', 'IE >= 8'] // pc
        // browsers: ['Android >= 4', 'iOS >= 6', 'and_uc > 9'] // wap
    })
});
fis.match(/^\/modules\/(.*\.css)$/i, {
    isMod: true,
    release: '${project.static}/$1',
    postprocessor: fis.plugin('autoprefixer', {
        browsers: ['> 1% in CN', "last 2 versions", "IE >= 8"] // pc
        // browsers: ["Android >= 4", "ChromeAndroid > 1%", "iOS >= 6"] // wap
    })
})
fis.match(/^\/modules\/(.*\.(?:png|jpg|gif))$/i, {
    release: '${project.static}/$1'
});

// 配置js
fis.match(/^\/modules\/(.*\.es)$/i, {
    parser: fis.plugin('babel-5.x'),
    rExt: 'js',
    isMod: true,
    release: '${project.static}/$1'
});
fis.match(/^\/modules\/(.*\.js)$/i, {
    isMod: true,
    release: '${project.static}/$1'
});

// ------ 配置前端模版 使用template.js
fis.match('**.tmpl', {
    parser: fis.plugin('template', {
        sTag: '<#',
        eTag: '#>',
        global: 'template'
    }),
    isJsLike: true,
    release : false
});

// ------ 配置模拟数据
fis.match('/test/**', {
  release: '$0'
});
fis.match('/test/server.conf', {
  release: '/config/server.conf'
});

/*************************打包规范*****************************/

// 因为是纯前端项目，依赖不能自断被加载进来，所以这里需要借助一个 loader 来完成，
// 注意：与后端结合的项目不需要此插件!!!
fis.match('::package', {
    // npm install [-g] fis3-postpackager-loader
    // 分析 __RESOURCE_MAP__ 结构，来解决资源加载问题
    postpackager: fis.plugin('loader', {
        resourceType: 'commonJs',
        useInlineMap: true // 资源映射表内嵌
    })
});

// 公用js
var map = {
    'prd-debug': {
        host: '',
        path: ''
    },
    'prd': {
        host: 'http://yanhaijing.com',
        path: '/${project.name}'
    }
};

fis.util.map(map, function (k, v) {
    var domain = v.host + v.path;

    fis.media(k)
        .match('**.{es,js}', {
            useHash: true,
            domain: domain
        })
        .match('**.{scss,css}', {
            useSprite: true,
            useHash: true,
            domain: domain
        })
        .match('::image', {
            useHash: true,
            domain: domain
        })
        .match('**/(*_{x,y,z}.png)', {
            release: '/pkg/$1'
        })
        // 启用打包插件，必须匹配 ::package
        .match('::package', {
            spriter: fis.plugin('csssprites', {
                layout: 'matrix',
                // scale: 0.5, // 移动端二倍图用
                margin: '10'
            }),
            postpackager: fis.plugin('loader', {
                allInOne: true,
            })
        })
        .match('/lib/es5-{shim,sham}.js', {
            packTo: '/pkg/es5-shim.js'
        })
        .match('/components/**.css', {
            packTo: '/pkg/components.css'
        })
        .match('/components/**.js', {
            packTo: '/pkg/components.js'
        })
        .match('/modules/**.{scss,css}', {
            packTo: '/pkg/modules.css'
        })
        .match('/modules/css/**.{scss,css}', {
            packTo: ''
        })
        .match('/modules/css/common.scss', {
            packTo: '/pkg/common.css'
        })
        .match('/modules/**.{es,js}', {
            packTo: '/pkg/modules.js'
        })
        .match('/modules/app/**.{es,js}', {
            packTo: '/pkg/aio.js'
        })
});

// 发布产品库
fis.media('prd')
    .match('**.{es,js}', {
        optimizer: fis.plugin('uglify-js')
    })
    .match('**.{scss,css}', {
        optimizer: fis.plugin('clean-css', {
            'keepBreaks': true //保持一个规则一个换行
        })
    });
<|endoftext|>


<commit_before>/*
 * @Copyright 2014 Vincent Petry <pvince81@owncloud.com>
 *
 * @author Vincent Petry
 * @author Felix Nüsse <felix.nuesse@t-online.de>
 *
 *
 * This file is licensed under the Affero General Public License version 3
 * or later.
 *
 * See the COPYING-README file.
 *
 */

(function () {

	/**
	 * @class OCA.Files.Navigation
	 * @classdesc Navigation control for the files app sidebar.
	 *
	 * @param $el element containing the navigation
	 */
	var Navigation = function ($el) {
		this.initialize($el);
	};

	/**
	 * @memberof OCA.Files
	 */
	Navigation.prototype = {

		/**
		 * Currently selected item in the list
		 */
		_activeItem: null,

		/**
		 * Currently selected container
		 */
		$currentContent: null,

		/**
		 * Key for the quick-acces-list
		 */
		$quickAccessListKey: 'sublist-favorites',
		/**
		 * Initializes the navigation from the given container
		 *
		 * @private
		 * @param $el element containing the navigation
		 */
		initialize: function ($el) {
			this.$el = $el;
			this._activeItem = null;
			this.$currentContent = null;
			this._setupEvents();

			this.setInitialQuickaccessSettings();
		},

		/**
		 * Setup UI events
		 */
		_setupEvents: function () {
			this.$el.on('click', 'li a', _.bind(this._onClickItem, this));
			this.$el.on('click', 'li button', _.bind(this._onClickMenuButton, this));

			var trashElement=$('.nav-trashbin');
			trashElement.droppable({
				over: function( event, ui ) {
					trashElement.addClass('dropzone-background');
				},
				out: function( event, ui ) {
					trashElement.removeClass('dropzone-background');
				},
				activate: function( event, ui ) {
					var elem=trashElement.find('a').first();
					elem.addClass('nav-icon-trashbin-starred').removeClass('nav-icon-trashbin');
				},
				deactivate: function( event, ui ) {
					var elem=trashElement.find('a').first();
					elem.addClass('nav-icon-trashbin').removeClass('nav-icon-trashbin-starred');
				},
				drop: function( event, ui ) {

					var $selectedFiles = $(ui.draggable);

					if (ui.helper.find('tr').size()===1) {
						var $tr = $selectedFiles.closest('tr');
						$selectedFiles.trigger('droppedOnTrash', $tr.attr('data-file'), $tr.attr('data-dir'));
					}else{
						var item = ui.helper.find('tr');
						for(var i=0; i<item.length;i++){
							$selectedFiles.trigger('droppedOnTrash', item[i].getAttribute('data-file'), item[i].getAttribute('data-dir'));
						}
					}
				}
			});
		},

		/**
		 * Returns the container of the currently active app.
		 *
		 * @return app container
		 */
		getActiveContainer: function () {
			return this.$currentContent;
		},

		/**
		 * Returns the currently active item
		 *
		 * @return item ID
		 */
		getActiveItem: function () {
			return this._activeItem;
		},

		/**
		 * Switch the currently selected item, mark it as selected and
		 * make the content container visible, if any.
		 *
		 * @param string itemId id of the navigation item to select
		 * @param array options "silent" to not trigger event
		 */
		setActiveItem: function (itemId, options) {
			var currentItem = this.$el.find('li[data-id="' + itemId + '"]');
			var itemDir = currentItem.data('dir');
			var itemView = currentItem.data('view');
			var oldItemId = this._activeItem;
			if (itemId === this._activeItem) {
				if (!options || !options.silent) {
					this.$el.trigger(
						new $.Event('itemChanged', {
							itemId: itemId,
							previousItemId: oldItemId,
							dir: itemDir,
							view: itemView
						})
					);
				}
				return;
			}
			this.$el.find('li a').removeClass('active');
			if (this.$currentContent) {
				this.$currentContent.addClass('hidden');
				this.$currentContent.trigger(jQuery.Event('hide'));
			}
			this._activeItem = itemId;
			currentItem.children('a').addClass('active');
			this.$currentContent = $('#app-content-' + (typeof itemView === 'string' && itemView !== '' ? itemView : itemId));
			this.$currentContent.removeClass('hidden');
			if (!options || !options.silent) {
				this.$currentContent.trigger(jQuery.Event('show', {
					itemId: itemId,
					previousItemId: oldItemId,
					dir: itemDir,
					view: itemView
				}));
				this.$el.trigger(
					new $.Event('itemChanged', {
						itemId: itemId,
						previousItemId: oldItemId,
						dir: itemDir,
						view: itemView
					})
				);
			}
		},

		/**
		 * Returns whether a given item exists
		 */
		itemExists: function (itemId) {
			return this.$el.find('li[data-id="' + itemId + '"]').length;
		},

		/**
		 * Event handler for when clicking on an item.
		 */
		_onClickItem: function (ev) {
			var $target = $(ev.target);
			var itemId = $target.closest('li').attr('data-id');
			if (!_.isUndefined(itemId)) {
				this.setActiveItem(itemId);
			}
			ev.preventDefault();
		},

		/**
		 * Event handler for clicking a button
		 */
		_onClickMenuButton: function (ev) {
			var $target = $(ev.target);
			var $menu = $target.parent('li');
			var itemId = $target.closest('button').attr('id');

			var collapsibleToggles = [];
			var dotmenuToggles = [];

			if ($menu.hasClass('collapsible') && $menu.data('expandedstate')) {
				$menu.toggleClass('open');
				var show = $menu.hasClass('open') ? 1 : 0;
				var key = $menu.data('expandedstate');
				$.post(OC.generateUrl("/apps/files/api/v1/toggleShowFolder/" + key), {show: show});
			}

			dotmenuToggles.forEach(function foundToggle (item) {
				if (item[0] === ("#" + itemId)) {
					document.getElementById(item[1]).classList.toggle('open');
				}
			});

			ev.preventDefault();
		},

		/**
		 * Sort initially as setup of sidebar for QuickAccess
		 */
		setInitialQuickaccessSettings: function () {
			var quickAccessKey = this.$quickAccessListKey;
			var quickAccessMenu = document.getElementById(quickAccessKey);
			if (quickAccessMenu) {
				var list = quickAccessMenu.getElementsByTagName('li');
				this.QuickSort(list, 0, list.length - 1);
			}
		},

		/**
		 * Sorting-Algorithm for QuickAccess
		 */
		QuickSort: function (list, start, end) {
			var lastMatch;
			if (list.length > 1) {
				lastMatch = this.quicksort_helper(list, start, end);
				if (start < lastMatch - 1) {
					this.QuickSort(list, start, lastMatch - 1);
				}
				if (lastMatch < end) {
					this.QuickSort(list, lastMatch, end);
				}
			}
		},

		/**
		 * Sorting-Algorithm-Helper for QuickAccess
		 */
		quicksort_helper: function (list, start, end) {
			var pivot = Math.floor((end + start) / 2);
			var pivotElement = this.getCompareValue(list, pivot);
			var i = start;
			var j = end;

			while (i <= j) {
				while (this.getCompareValue(list, i) < pivotElement) {
					i++;
				}
				while (this.getCompareValue(list, j) > pivotElement) {
					j--;
				}
				if (i <= j) {
					this.swap(list, i, j);
					i++;
					j--;
				}
			}
			return i;
		},

		/**
		 * Sorting-Algorithm-Helper for QuickAccess
		 * This method allows easy access to the element which is sorted by.
		 */
		getCompareValue: function (nodes, int, strategy) {
				return nodes[int].getElementsByTagName('a')[0].innerHTML.toLowerCase();
		},

		/**
		 * Sorting-Algorithm-Helper for QuickAccess
		 * This method allows easy swapping of elements.
		 */
		swap: function (list, j, i) {
			var before = function(node, insertNode) {
				node.parentNode.insertBefore(insertNode, node);
			}
			before(list[i], list[j]);
			before(list[j], list[i]);
		}

	};

	OCA.Files.Navigation = Navigation;

})();

<commit_msg>Make code format more consistent with the rest of the file<commit_after>/*
 * @Copyright 2014 Vincent Petry <pvince81@owncloud.com>
 *
 * @author Vincent Petry
 * @author Felix Nüsse <felix.nuesse@t-online.de>
 *
 *
 * This file is licensed under the Affero General Public License version 3
 * or later.
 *
 * See the COPYING-README file.
 *
 */

(function () {

	/**
	 * @class OCA.Files.Navigation
	 * @classdesc Navigation control for the files app sidebar.
	 *
	 * @param $el element containing the navigation
	 */
	var Navigation = function ($el) {
		this.initialize($el);
	};

	/**
	 * @memberof OCA.Files
	 */
	Navigation.prototype = {

		/**
		 * Currently selected item in the list
		 */
		_activeItem: null,

		/**
		 * Currently selected container
		 */
		$currentContent: null,

		/**
		 * Key for the quick-acces-list
		 */
		$quickAccessListKey: 'sublist-favorites',
		/**
		 * Initializes the navigation from the given container
		 *
		 * @private
		 * @param $el element containing the navigation
		 */
		initialize: function ($el) {
			this.$el = $el;
			this._activeItem = null;
			this.$currentContent = null;
			this._setupEvents();

			this.setInitialQuickaccessSettings();
		},

		/**
		 * Setup UI events
		 */
		_setupEvents: function () {
			this.$el.on('click', 'li a', _.bind(this._onClickItem, this));
			this.$el.on('click', 'li button', _.bind(this._onClickMenuButton, this));

			var trashElement = $('.nav-trashbin');
			trashElement.droppable({
				over: function (event, ui) {
					trashElement.addClass('dropzone-background');
				},
				out: function (event, ui) {
					trashElement.removeClass('dropzone-background');
				},
				activate: function (event, ui) {
					var elem = trashElement.find('a').first();
					elem.addClass('nav-icon-trashbin-starred').removeClass('nav-icon-trashbin');
				},
				deactivate: function (event, ui) {
					var elem = trashElement.find('a').first();
					elem.addClass('nav-icon-trashbin').removeClass('nav-icon-trashbin-starred');
				},
				drop: function (event, ui) {
					var $selectedFiles = $(ui.draggable);

					if (ui.helper.find('tr').size() === 1) {
						var $tr = $selectedFiles.closest('tr');
						$selectedFiles.trigger('droppedOnTrash', $tr.attr('data-file'), $tr.attr('data-dir'));
					} else {
						var item = ui.helper.find('tr');
						for (var i = 0; i < item.length; i++) {
							$selectedFiles.trigger('droppedOnTrash', item[i].getAttribute('data-file'), item[i].getAttribute('data-dir'));
						}
					}
				}
			});
		},

		/**
		 * Returns the container of the currently active app.
		 *
		 * @return app container
		 */
		getActiveContainer: function () {
			return this.$currentContent;
		},

		/**
		 * Returns the currently active item
		 *
		 * @return item ID
		 */
		getActiveItem: function () {
			return this._activeItem;
		},

		/**
		 * Switch the currently selected item, mark it as selected and
		 * make the content container visible, if any.
		 *
		 * @param string itemId id of the navigation item to select
		 * @param array options "silent" to not trigger event
		 */
		setActiveItem: function (itemId, options) {
			var currentItem = this.$el.find('li[data-id="' + itemId + '"]');
			var itemDir = currentItem.data('dir');
			var itemView = currentItem.data('view');
			var oldItemId = this._activeItem;
			if (itemId === this._activeItem) {
				if (!options || !options.silent) {
					this.$el.trigger(
						new $.Event('itemChanged', {
							itemId: itemId,
							previousItemId: oldItemId,
							dir: itemDir,
							view: itemView
						})
					);
				}
				return;
			}
			this.$el.find('li a').removeClass('active');
			if (this.$currentContent) {
				this.$currentContent.addClass('hidden');
				this.$currentContent.trigger(jQuery.Event('hide'));
			}
			this._activeItem = itemId;
			currentItem.children('a').addClass('active');
			this.$currentContent = $('#app-content-' + (typeof itemView === 'string' && itemView !== '' ? itemView : itemId));
			this.$currentContent.removeClass('hidden');
			if (!options || !options.silent) {
				this.$currentContent.trigger(jQuery.Event('show', {
					itemId: itemId,
					previousItemId: oldItemId,
					dir: itemDir,
					view: itemView
				}));
				this.$el.trigger(
					new $.Event('itemChanged', {
						itemId: itemId,
						previousItemId: oldItemId,
						dir: itemDir,
						view: itemView
					})
				);
			}
		},

		/**
		 * Returns whether a given item exists
		 */
		itemExists: function (itemId) {
			return this.$el.find('li[data-id="' + itemId + '"]').length;
		},

		/**
		 * Event handler for when clicking on an item.
		 */
		_onClickItem: function (ev) {
			var $target = $(ev.target);
			var itemId = $target.closest('li').attr('data-id');
			if (!_.isUndefined(itemId)) {
				this.setActiveItem(itemId);
			}
			ev.preventDefault();
		},

		/**
		 * Event handler for clicking a button
		 */
		_onClickMenuButton: function (ev) {
			var $target = $(ev.target);
			var $menu = $target.parent('li');
			var itemId = $target.closest('button').attr('id');

			var collapsibleToggles = [];
			var dotmenuToggles = [];

			if ($menu.hasClass('collapsible') && $menu.data('expandedstate')) {
				$menu.toggleClass('open');
				var show = $menu.hasClass('open') ? 1 : 0;
				var key = $menu.data('expandedstate');
				$.post(OC.generateUrl("/apps/files/api/v1/toggleShowFolder/" + key), {show: show});
			}

			dotmenuToggles.forEach(function foundToggle (item) {
				if (item[0] === ("#" + itemId)) {
					document.getElementById(item[1]).classList.toggle('open');
				}
			});

			ev.preventDefault();
		},

		/**
		 * Sort initially as setup of sidebar for QuickAccess
		 */
		setInitialQuickaccessSettings: function () {
			var quickAccessKey = this.$quickAccessListKey;
			var quickAccessMenu = document.getElementById(quickAccessKey);
			if (quickAccessMenu) {
				var list = quickAccessMenu.getElementsByTagName('li');
				this.QuickSort(list, 0, list.length - 1);
			}
		},

		/**
		 * Sorting-Algorithm for QuickAccess
		 */
		QuickSort: function (list, start, end) {
			var lastMatch;
			if (list.length > 1) {
				lastMatch = this.quicksort_helper(list, start, end);
				if (start < lastMatch - 1) {
					this.QuickSort(list, start, lastMatch - 1);
				}
				if (lastMatch < end) {
					this.QuickSort(list, lastMatch, end);
				}
			}
		},

		/**
		 * Sorting-Algorithm-Helper for QuickAccess
		 */
		quicksort_helper: function (list, start, end) {
			var pivot = Math.floor((end + start) / 2);
			var pivotElement = this.getCompareValue(list, pivot);
			var i = start;
			var j = end;

			while (i <= j) {
				while (this.getCompareValue(list, i) < pivotElement) {
					i++;
				}
				while (this.getCompareValue(list, j) > pivotElement) {
					j--;
				}
				if (i <= j) {
					this.swap(list, i, j);
					i++;
					j--;
				}
			}
			return i;
		},

		/**
		 * Sorting-Algorithm-Helper for QuickAccess
		 * This method allows easy access to the element which is sorted by.
		 */
		getCompareValue: function (nodes, int, strategy) {
				return nodes[int].getElementsByTagName('a')[0].innerHTML.toLowerCase();
		},

		/**
		 * Sorting-Algorithm-Helper for QuickAccess
		 * This method allows easy swapping of elements.
		 */
		swap: function (list, j, i) {
			var before = function(node, insertNode) {
				node.parentNode.insertBefore(insertNode, node);
			}
			before(list[i], list[j]);
			before(list[j], list[i]);
		}

	};

	OCA.Files.Navigation = Navigation;

})();

<|endoftext|>