<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import { afterEach, beforeEach, describe, it } from 'mocha';
import { default as chai, expect } from 'chai';
import { formatWarning } from '../assertValidName';

/* eslint-disable no-console */

/**
 * Helper for dedenting indented template literals. This helps us to
 * keep the tests pretty.
 */
function dedent(string) {
  // Get lines, discarding empty leading and trailing lines.
  const lines = string.replace(/^[ \t]*\n|\n[ \t]*$/g, '').split('\n');

  // Find smallest indent.
  const indent = lines.reduce((currentMinimum, line) => {
    const whitespace = line.match(/^ +/);
    return Math.min(whitespace ? whitespace[0].length : 0, currentMinimum);
  }, Infinity);

  // Remove indent from each line.
  return lines.map(line => line.slice(indent)).join('\n');
}

/**
 * Convenience method for creating an Error object with a defined stack.
 */
function createErrorObject(message, stack) {
  const error = new Error(message);
  error.stack = stack;
  return error;
}

describe('assertValidName()', () => {
  let assertValidName;
  let noNameWarning;
  let warn;

  beforeEach(() => {
    noNameWarning = process.env.GRAPHQL_NO_NAME_WARNING;
    delete process.env.GRAPHQL_NO_NAME_WARNING;
    warn = console.warn;
    console.warn = chai.spy();

    // Make sure module-internal state is reset for each test.
    delete require.cache[require.resolve('../assertValidName')];
    assertValidName = require('../assertValidName').assertValidName;
  });

  afterEach(() => {
    console.warn = warn;
    if (noNameWarning === undefined) {
      delete process.env.GRAPHQL_NO_NAME_WARNING;
    } else {
      process.env.GRAPHQL_NO_NAME_WARNING = noNameWarning;
    }
  });

  it('warns against use of leading double underscores', () => {
    assertValidName('__bad');
    /* eslint-disable no-unused-expressions */
    expect(console.warn).to.have.been.called.once;
    /* eslint-enable no-unused-expressions */
    expect(console.warn.__spy.calls[0][0]).to.match(/must not begin with/);
  });

  it('warns exactly once even in the presence of multiple violations', () => {
    assertValidName('__bad');
    assertValidName('__alsoBad');
    /* eslint-disable no-unused-expressions */
    expect(console.warn).to.have.been.called.once;
    /* eslint-enable no-unused-expressions */
  });

  it('throws for non-strings', () => {
    expect(() => assertValidName({})).to.throw(/Must be named/);
  });

  it('throws for names with invalid characters', () => {
    expect(() => assertValidName('>--()-->')).to.throw(/Names must match/);
  });

  it('does not warn during introspection', () => {
    assertValidName('__bad', true);
    expect(console.warn).not.to.have.been.called();
  });

  it('does not warn when GRAPHQL_NO_NAME_WARNING is in effect', () => {
    process.env.GRAPHQL_NO_NAME_WARNING = '1';
    assertValidName('__bad', true);
    expect(console.warn).not.to.have.been.called();
  });
});

describe('formatWarning()', () => {
  it('formats given a Chrome-style stack property', () => {
    const chromeStack = dedent(`
      Error: foo
        at z (<anonymous>:1:21)
        at y (<anonymous>:1:15)
        at x (<anonymous>:1:15)
        at <anonymous>:1:6
    `);
    const error = createErrorObject('foo', chromeStack);
    expect(formatWarning(error)).to.equal(
      dedent(`
      foo
        at z (<anonymous>:1:21)
        at y (<anonymous>:1:15)
        at x (<anonymous>:1:15)
        at <anonymous>:1:6
    `),
    );
  });

  it('formats given a Node-style stack property', () => {
    const nodeStack = dedent(`
      Error: foo
        at z (repl:1:29)
        at y (repl:1:23)
        at x (repl:1:23)
        at repl:1:6
        at ContextifyScript.Script.runInThisContext (vm.js:23:33)
        at REPLServer.defaultEval (repl.js:340:29)
        at bound (domain.js:280:14)
        at REPLServer.runBound [as eval] (domain.js:293:12)
        at REPLServer.onLine (repl.js:537:10)
        at emitOne (events.js:101:20)
    `);
    const error = createErrorObject('foo', nodeStack);
    expect(formatWarning(error)).to.equal(
      dedent(`
      foo
        at z (repl:1:29)
        at y (repl:1:23)
        at x (repl:1:23)
        at repl:1:6
        at ContextifyScript.Script.runInThisContext (vm.js:23:33)
        at REPLServer.defaultEval (repl.js:340:29)
        at bound (domain.js:280:14)
        at REPLServer.runBound [as eval] (domain.js:293:12)
        at REPLServer.onLine (repl.js:537:10)
        at emitOne (events.js:101:20)
    `),
    );
  });

  it('formats given a Firefox-style stack property', () => {
    const firefoxStack = dedent(`
      z@debugger eval code:1:20
      y@debugger eval code:1:14
      x@debugger eval code:1:14
      @debugger eval code:1:5
    `);
    const error = createErrorObject('foo', firefoxStack);
    expect(formatWarning(error)).to.equal(
      dedent(`
      foo
      z@debugger eval code:1:20
      y@debugger eval code:1:14
      x@debugger eval code:1:14
      @debugger eval code:1:5
    `),
    );
  });

  it('formats given a Safari-style stack property', () => {
    const safariStack = dedent(`
      z
      y
      x
      global code
      evaluateWithScopeExtension@[native code]
      _evaluateOn
      _evaluateAndWrap
      evaluate
    `);
    const error = createErrorObject('foo', safariStack);
    expect(formatWarning(error)).to.equal(
      dedent(`
      foo
      z
      y
      x
      global code
      evaluateWithScopeExtension@[native code]
      _evaluateOn
      _evaluateAndWrap
      evaluate
    `),
    );
  });

  it('formats in the absence of a stack property', () => {
    const error = createErrorObject('foo');
    expect(formatWarning(error)).to.equal('foo');
  });
});
<commit_msg>Remove duplicate dedent definition<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import { afterEach, beforeEach, describe, it } from 'mocha';
import chai, { expect } from 'chai';
import { formatWarning } from '../assertValidName';
import dedent from '../../jsutils/dedent';

/* eslint-disable no-console */

/**
 * Convenience method for creating an Error object with a defined stack.
 */
function createErrorObject(message, stack) {
  const error = new Error(message);
  error.stack = stack;
  return error;
}

describe('assertValidName()', () => {
  let assertValidName;
  let noNameWarning;
  let warn;

  beforeEach(() => {
    noNameWarning = process.env.GRAPHQL_NO_NAME_WARNING;
    delete process.env.GRAPHQL_NO_NAME_WARNING;
    warn = console.warn;
    console.warn = chai.spy();

    // Make sure module-internal state is reset for each test.
    delete require.cache[require.resolve('../assertValidName')];
    assertValidName = require('../assertValidName').assertValidName;
  });

  afterEach(() => {
    console.warn = warn;
    if (noNameWarning === undefined) {
      delete process.env.GRAPHQL_NO_NAME_WARNING;
    } else {
      process.env.GRAPHQL_NO_NAME_WARNING = noNameWarning;
    }
  });

  it('warns against use of leading double underscores', () => {
    assertValidName('__bad');
    /* eslint-disable no-unused-expressions */
    expect(console.warn).to.have.been.called.once;
    /* eslint-enable no-unused-expressions */
    expect(console.warn.__spy.calls[0][0]).to.match(/must not begin with/);
  });

  it('warns exactly once even in the presence of multiple violations', () => {
    assertValidName('__bad');
    assertValidName('__alsoBad');
    /* eslint-disable no-unused-expressions */
    expect(console.warn).to.have.been.called.once;
    /* eslint-enable no-unused-expressions */
  });

  it('throws for non-strings', () => {
    expect(() => assertValidName({})).to.throw(/Must be named/);
  });

  it('throws for names with invalid characters', () => {
    expect(() => assertValidName('>--()-->')).to.throw(/Names must match/);
  });

  it('does not warn during introspection', () => {
    assertValidName('__bad', true);
    expect(console.warn).not.to.have.been.called();
  });

  it('does not warn when GRAPHQL_NO_NAME_WARNING is in effect', () => {
    process.env.GRAPHQL_NO_NAME_WARNING = '1';
    assertValidName('__bad', true);
    expect(console.warn).not.to.have.been.called();
  });
});

describe('formatWarning()', () => {
  it('formats given a Chrome-style stack property', () => {
    const chromeStack = dedent`
      Error: foo
        at z (<anonymous>:1:21)
        at y (<anonymous>:1:15)
        at x (<anonymous>:1:15)
        at <anonymous>:1:6`;
    const error = createErrorObject('foo', chromeStack);
    expect(formatWarning(error)).to.equal(
      dedent`
      foo
        at z (<anonymous>:1:21)
        at y (<anonymous>:1:15)
        at x (<anonymous>:1:15)
        at <anonymous>:1:6`,
    );
  });

  it('formats given a Node-style stack property', () => {
    const nodeStack = dedent`
      Error: foo
        at z (repl:1:29)
        at y (repl:1:23)
        at x (repl:1:23)
        at repl:1:6
        at ContextifyScript.Script.runInThisContext (vm.js:23:33)
        at REPLServer.defaultEval (repl.js:340:29)
        at bound (domain.js:280:14)
        at REPLServer.runBound [as eval] (domain.js:293:12)
        at REPLServer.onLine (repl.js:537:10)
        at emitOne (events.js:101:20)`;
    const error = createErrorObject('foo', nodeStack);
    expect(formatWarning(error)).to.equal(
      dedent`
      foo
        at z (repl:1:29)
        at y (repl:1:23)
        at x (repl:1:23)
        at repl:1:6
        at ContextifyScript.Script.runInThisContext (vm.js:23:33)
        at REPLServer.defaultEval (repl.js:340:29)
        at bound (domain.js:280:14)
        at REPLServer.runBound [as eval] (domain.js:293:12)
        at REPLServer.onLine (repl.js:537:10)
        at emitOne (events.js:101:20)`,
    );
  });

  it('formats given a Firefox-style stack property', () => {
    const firefoxStack = dedent`
      z@debugger eval code:1:20
      y@debugger eval code:1:14
      x@debugger eval code:1:14
      @debugger eval code:1:5`;
    const error = createErrorObject('foo', firefoxStack);
    expect(formatWarning(error)).to.equal(
      dedent`
      foo
      z@debugger eval code:1:20
      y@debugger eval code:1:14
      x@debugger eval code:1:14
      @debugger eval code:1:5`,
    );
  });

  it('formats given a Safari-style stack property', () => {
    const safariStack = dedent`
      z
      y
      x
      global code
      evaluateWithScopeExtension@[native code]
      _evaluateOn
      _evaluateAndWrap
      evaluate`;
    const error = createErrorObject('foo', safariStack);
    expect(formatWarning(error)).to.equal(
      dedent`
      foo
      z
      y
      x
      global code
      evaluateWithScopeExtension@[native code]
      _evaluateOn
      _evaluateAndWrap
      evaluate`,
    );
  });

  it('formats in the absence of a stack property', () => {
    const error = createErrorObject('foo');
    expect(formatWarning(error)).to.equal('foo');
  });
});
<|endoftext|>


<commit_before>// Except as noted, this content is licensed under Creative Commons
// Attribution 3.0

/* A little code to ease navigation of these documents.
 *
 * On window load we:
 *  + Generate a table of contents (godocs_generateTOC)
 *  + Add links up to the top of the doc from each section (godocs_addTopLinks)
 */

/* We want to do some stuff on page load (after the HTML is rendered).
   So listen for that:
 */
function bindEvent(el, e, fn) {
  if (el.addEventListener){
    el.addEventListener(e, fn, false);
  } else if (el.attachEvent){
    el.attachEvent('on'+e, fn);
  }
}
bindEvent(window, 'load', godocs_onload);

function godocs_onload() {
  godocs_bindSearchEvents();
  godocs_generateTOC();
  godocs_addTopLinks();
}

function godocs_bindSearchEvents() {
  var search = document.getElementById('search');
  function clearInactive() {
    if (search.className == "inactive") {
      search.value = "";
      search.className = "";
    }
  }
  function restoreInactive() {
    if (search.value != "") {
      return;
    }
    if (search.type != "search") {
      search.value = search.getAttribute("placeholder");
    }
    search.className = "inactive";
  }
  restoreInactive();
  bindEvent(search, 'focus', clearInactive);
  bindEvent(search, 'blur', restoreInactive);
}

/* Generates a table of contents: looks for h2 and h3 elements and generates
 * links.  "Decorates" the element with id=="nav" with this table of contents.
 */
function godocs_generateTOC() {
  var navbar = document.getElementById('nav');
  if (!navbar) { return; }

  var toc_items = [];

  var i;
  for (i = 0; i < navbar.parentNode.childNodes.length; i++) {
    var node = navbar.parentNode.childNodes[i];
    if ((node.tagName == 'h2') || (node.tagName == 'H2')) {
      if (!node.id) {
        node.id = 'tmp_' + i;
      }
      var text = godocs_nodeToText(node);
      if (!text) { continue; }

      var textNode = document.createTextNode(text);

      var link = document.createElement('a');
      link.href = '#' + node.id;
      link.appendChild(textNode);

      // Then create the item itself
      var item = document.createElement('dt');

      item.appendChild(link);
      toc_items.push(item);
    }
    if ((node.tagName == 'h3') || (node.tagName == 'H3')) {
      if (!node.id) {
        node.id = 'tmp_' + i;
      }
      var text = godocs_nodeToText(node);
      if (!text) { continue; }

      var textNode = document.createTextNode(text);

      var link = document.createElement('a');
      link.href = '#' + node.id;
      link.appendChild(textNode);

      // Then create the item itself
      var item = document.createElement('dd');

      item.appendChild(link);
      toc_items.push(item);
    }
  }

  if (!toc_items.length) { return; }

  var dl1 = document.createElement('dl');
  var dl2 = document.createElement('dl');

  var split_index = (toc_items.length / 2) + 1;
  if (split_index < 8) {
    split_index = toc_items.length;
  }

  for (i = 0; i < split_index; i++) {
    dl1.appendChild(toc_items[i]);
  }
  for (/* keep using i */; i < toc_items.length; i++) {
    dl2.appendChild(toc_items[i]);
  }

  var tocTable = document.createElement('table');
  navbar.appendChild(tocTable);
  tocTable.className = 'unruled';
  var tocBody = document.createElement('tbody');
  tocTable.appendChild(tocBody);

  var tocRow = document.createElement('tr');
  tocBody.appendChild(tocRow);

  // 1st column
  var tocCell = document.createElement('td');
  tocCell.className = 'first';
  tocRow.appendChild(tocCell);
  tocCell.appendChild(dl1);

  // 2nd column
  tocCell = document.createElement('td');
  tocRow.appendChild(tocCell);
  tocCell.appendChild(dl2);
}

/* Returns the "This sweet header" from
This sweet header
.
 * Takes a node, returns a string.
 */
function godocs_nodeToText(node) {
  var TEXT_NODE = 3; // Defined in Mozilla but not MSIE :(

  var text = '';
  for (var j = 0; j != node.childNodes.length; j++) {
    var child = node.childNodes[j];
    if (child.nodeType == TEXT_NODE) {
      if (child.nodeValue != '[Top]') { //ok, that's a hack, but it works.
        text = text + child.nodeValue;
      }
    } else {
      text = text + godocs_nodeToText(child);
    }
  }
  return text;
}

/* For each H2 heading, add a link up to the #top of the document.
 * (As part of this: ensure existence of 'top' named anchor link
 * (theoretically at doc's top).)
 */
function godocs_addTopLinks() {
  /* Make sure there's a "top" to link to. */
  var top = document.getElementById('top');
  if (!top) {
    document.body.id = 'top';
  }

  if (!document.getElementsByTagName) return; // no browser support

  var headers = document.getElementsByTagName('h2');

  for (var i = 0; i < headers.length; i++) {
    var span = document.createElement('span');
    span.className = 'navtop';
    var link = document.createElement('a');
    span.appendChild(link);
    link.href = '#top';
    var textNode = document.createTextNode('[Top]');
    link.appendChild(textNode);
    headers[i].appendChild(span);
  }
}
<commit_msg>godoc: if there is no search box, don't run the respective JS code<commit_after>// Except as noted, this content is licensed under Creative Commons
// Attribution 3.0

/* A little code to ease navigation of these documents.
 *
 * On window load we:
 *  + Generate a table of contents (godocs_generateTOC)
 *  + Add links up to the top of the doc from each section (godocs_addTopLinks)
 */

/* We want to do some stuff on page load (after the HTML is rendered).
   So listen for that:
 */
function bindEvent(el, e, fn) {
  if (el.addEventListener){
    el.addEventListener(e, fn, false);
  } else if (el.attachEvent){
    el.attachEvent('on'+e, fn);
  }
}
bindEvent(window, 'load', godocs_onload);

function godocs_onload() {
  godocs_bindSearchEvents();
  godocs_generateTOC();
  godocs_addTopLinks();
}

function godocs_bindSearchEvents() {
  var search = document.getElementById('search');
  if (!search) {
    // no search box (index disabled)
    return;
  }
  function clearInactive() {
    if (search.className == "inactive") {
      search.value = "";
      search.className = "";
    }
  }
  function restoreInactive() {
    if (search.value != "") {
      return;
    }
    if (search.type != "search") {
      search.value = search.getAttribute("placeholder");
    }
    search.className = "inactive";
  }
  restoreInactive();
  bindEvent(search, 'focus', clearInactive);
  bindEvent(search, 'blur', restoreInactive);
}

/* Generates a table of contents: looks for h2 and h3 elements and generates
 * links.  "Decorates" the element with id=="nav" with this table of contents.
 */
function godocs_generateTOC() {
  var navbar = document.getElementById('nav');
  if (!navbar) { return; }

  var toc_items = [];

  var i;
  for (i = 0; i < navbar.parentNode.childNodes.length; i++) {
    var node = navbar.parentNode.childNodes[i];
    if ((node.tagName == 'h2') || (node.tagName == 'H2')) {
      if (!node.id) {
        node.id = 'tmp_' + i;
      }
      var text = godocs_nodeToText(node);
      if (!text) { continue; }

      var textNode = document.createTextNode(text);

      var link = document.createElement('a');
      link.href = '#' + node.id;
      link.appendChild(textNode);

      // Then create the item itself
      var item = document.createElement('dt');

      item.appendChild(link);
      toc_items.push(item);
    }
    if ((node.tagName == 'h3') || (node.tagName == 'H3')) {
      if (!node.id) {
        node.id = 'tmp_' + i;
      }
      var text = godocs_nodeToText(node);
      if (!text) { continue; }

      var textNode = document.createTextNode(text);

      var link = document.createElement('a');
      link.href = '#' + node.id;
      link.appendChild(textNode);

      // Then create the item itself
      var item = document.createElement('dd');

      item.appendChild(link);
      toc_items.push(item);
    }
  }

  if (!toc_items.length) { return; }

  var dl1 = document.createElement('dl');
  var dl2 = document.createElement('dl');

  var split_index = (toc_items.length / 2) + 1;
  if (split_index < 8) {
    split_index = toc_items.length;
  }

  for (i = 0; i < split_index; i++) {
    dl1.appendChild(toc_items[i]);
  }
  for (/* keep using i */; i < toc_items.length; i++) {
    dl2.appendChild(toc_items[i]);
  }

  var tocTable = document.createElement('table');
  navbar.appendChild(tocTable);
  tocTable.className = 'unruled';
  var tocBody = document.createElement('tbody');
  tocTable.appendChild(tocBody);

  var tocRow = document.createElement('tr');
  tocBody.appendChild(tocRow);

  // 1st column
  var tocCell = document.createElement('td');
  tocCell.className = 'first';
  tocRow.appendChild(tocCell);
  tocCell.appendChild(dl1);

  // 2nd column
  tocCell = document.createElement('td');
  tocRow.appendChild(tocCell);
  tocCell.appendChild(dl2);
}

/* Returns the "This sweet header" from
This sweet header
.
 * Takes a node, returns a string.
 */
function godocs_nodeToText(node) {
  var TEXT_NODE = 3; // Defined in Mozilla but not MSIE :(

  var text = '';
  for (var j = 0; j != node.childNodes.length; j++) {
    var child = node.childNodes[j];
    if (child.nodeType == TEXT_NODE) {
      if (child.nodeValue != '[Top]') { //ok, that's a hack, but it works.
        text = text + child.nodeValue;
      }
    } else {
      text = text + godocs_nodeToText(child);
    }
  }
  return text;
}

/* For each H2 heading, add a link up to the #top of the document.
 * (As part of this: ensure existence of 'top' named anchor link
 * (theoretically at doc's top).)
 */
function godocs_addTopLinks() {
  /* Make sure there's a "top" to link to. */
  var top = document.getElementById('top');
  if (!top) {
    document.body.id = 'top';
  }

  if (!document.getElementsByTagName) return; // no browser support

  var headers = document.getElementsByTagName('h2');

  for (var i = 0; i < headers.length; i++) {
    var span = document.createElement('span');
    span.className = 'navtop';
    var link = document.createElement('a');
    span.appendChild(link);
    link.href = '#top';
    var textNode = document.createTextNode('[Top]');
    link.appendChild(textNode);
    headers[i].appendChild(span);
  }
}
<|endoftext|>


<commit_before>db.trophyKind.drop();
db.trophyKind.insert({
  _id: 'zugMiracle',
  name: 'Zug miracle',
  url: '//lichess.org/faq#trophies',
  order: NumberInt(1),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'wayOfBerserk',
  name: 'The way of Berserk',
  icon: '',
  url: '//lichess.org/faq#trophies',
  klass: 'fire-trophy',
  order: NumberInt(2),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'marathonWinner',
  name: 'Marathon Winner',
  icon: '',
  klass: 'fire-trophy',
  order: NumberInt(3),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'marathonTopTen',
  name: 'Marathon Top 10',
  icon: '',
  klass: 'fire-trophy',
  order: NumberInt(4),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'marathonTopFifty',
  name: 'Marathon Top 50',
  icon: '',
  klass: 'fire-trophy',
  order: NumberInt(5),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'marathonTopHundred',
  name: 'Marathon Top 100',
  icon: '',
  klass: 'fire-trophy',
  order: NumberInt(6),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'marathonTopFivehundred',
  name: 'Marathon Top 500',
  icon: '',
  klass: 'fire-trophy',
  order: NumberInt(7),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'marathonSurvivor',
  name: 'Marathon #1 survivor',
  icon: '',
  url: '//lichess.org/blog/VXF45yYAAPQgLH4d/chess-marathon-1',
  klass: 'fire-trophy',
  order: NumberInt(8),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'developer',
  name: 'Lichess developer',
  icon: '',
  url: 'https://github.com/ornicar/lila/graphs/contributors',
  klass: 'icon3d',
  order: NumberInt(100),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'moderator',
  name: 'Lichess moderator',
  icon: '',
  url: '//lichess.org/report',
  klass: 'icon3d',
  order: NumberInt(101),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'verified',
  name: 'Verified account',
  icon: '',
  klass: 'icon3d',
  order: NumberInt(102),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'contentTeam',
  name: 'Lichess Content Team',
  icon: '',
  klass: 'icon3d',
  order: NumberInt(103),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'zhwc17',
  name: 'Crazyhouse champion 2017',
  url: '//lichess.org/blog/WMnMzSEAAMgA3oAW/crazyhouse-world-championship-the-candidates',
  order: NumberInt(1),
  withCustomImage: true,
});
db.trophyKind.insert({
  _id: 'zhwc18',
  name: 'Crazyhouse champion 2018',
  url: '//lichess.org/forum/team-crazyhouse-world-championship/opperwezen-the-2nd-cwc',
  order: NumberInt(1),
  withCustomImage: true,
});
db.trophyKind.insert({
  _id: 'atomicwc16',
  name: 'Atomic World Champion 2016',
  url: '//lichess.org/forum/team-atomic-wc/championship-final',
  order: NumberInt(1),
  withCustomImage: true,
});
db.trophyKind.insert({
  _id: 'atomicwc17',
  name: 'Atomic World Champion 2017',
  url: '//lichess.org/forum/team-atomic-wc/awc-2017-its-final-time',
  order: NumberInt(1),
  withCustomImage: true,
});
db.trophyKind.insert({
  _id: 'atomicwc18',
  name: 'Atomic World Champion 2018',
  url: '//lichess.org/forum/team-atomic-wc/announcement-awc-2018',
  order: NumberInt(1),
  withCustomImage: true,
});
db.trophyKind.insert({
  _id: 'acwc18',
  name: 'Antichess World Champion 2018',
  url: '//lichess.org/forum/team-antichess-wc/congratulations-to-our-new-antichess-world-champion',
  order: NumberInt(1),
  withCustomImage: true,
});
<commit_msg>Prettier<commit_after>db.trophyKind.drop();
db.trophyKind.insert({
  _id: 'zugMiracle',
  name: 'Zug miracle',
  url: '//lichess.org/faq#trophies',
  order: NumberInt(1),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'wayOfBerserk',
  name: 'The way of Berserk',
  icon: '',
  url: '//lichess.org/faq#trophies',
  klass: 'fire-trophy',
  order: NumberInt(2),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'marathonWinner',
  name: 'Marathon Winner',
  icon: '',
  klass: 'fire-trophy',
  order: NumberInt(3),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'marathonTopTen',
  name: 'Marathon Top 10',
  icon: '',
  klass: 'fire-trophy',
  order: NumberInt(4),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'marathonTopFifty',
  name: 'Marathon Top 50',
  icon: '',
  klass: 'fire-trophy',
  order: NumberInt(5),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'marathonTopHundred',
  name: 'Marathon Top 100',
  icon: '',
  klass: 'fire-trophy',
  order: NumberInt(6),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'marathonTopFivehundred',
  name: 'Marathon Top 500',
  icon: '',
  klass: 'fire-trophy',
  order: NumberInt(7),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'marathonSurvivor',
  name: 'Marathon #1 survivor',
  icon: '',
  url: '//lichess.org/blog/VXF45yYAAPQgLH4d/chess-marathon-1',
  klass: 'fire-trophy',
  order: NumberInt(8),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'developer',
  name: 'Lichess developer',
  icon: '',
  url: 'https://github.com/ornicar/lila/graphs/contributors',
  klass: 'icon3d',
  order: NumberInt(100),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'moderator',
  name: 'Lichess moderator',
  icon: '',
  url: '//lichess.org/report',
  klass: 'icon3d',
  order: NumberInt(101),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'verified',
  name: 'Verified account',
  icon: '',
  klass: 'icon3d',
  order: NumberInt(102),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'contentTeam',
  name: 'Lichess Content Team',
  icon: '',
  klass: 'icon3d',
  order: NumberInt(103),
  withCustomImage: false,
});
db.trophyKind.insert({
  _id: 'zhwc17',
  name: 'Crazyhouse champion 2017',
  url: '//lichess.org/blog/WMnMzSEAAMgA3oAW/crazyhouse-world-championship-the-candidates',
  order: NumberInt(1),
  withCustomImage: true,
});
db.trophyKind.insert({
  _id: 'zhwc18',
  name: 'Crazyhouse champion 2018',
  url: '//lichess.org/forum/team-crazyhouse-world-championship/opperwezen-the-2nd-cwc',
  order: NumberInt(1),
  withCustomImage: true,
});
db.trophyKind.insert({
  _id: 'atomicwc16',
  name: 'Atomic World Champion 2016',
  url: '//lichess.org/forum/team-atomic-wc/championship-final',
  order: NumberInt(1),
  withCustomImage: true,
});
db.trophyKind.insert({
  _id: 'atomicwc17',
  name: 'Atomic World Champion 2017',
  url: '//lichess.org/forum/team-atomic-wc/awc-2017-its-final-time',
  order: NumberInt(1),
  withCustomImage: true,
});
db.trophyKind.insert({
  _id: 'atomicwc18',
  name: 'Atomic World Champion 2018',
  url: '//lichess.org/forum/team-atomic-wc/announcement-awc-2018',
  order: NumberInt(1),
  withCustomImage: true,
});
db.trophyKind.insert({
  _id: 'acwc18',
  name: 'Antichess World Champion 2018',
  url: '//lichess.org/forum/team-antichess-wc/congratulations-to-our-new-antichess-world-champion',
  order: NumberInt(1),
  withCustomImage: true,
});
<|endoftext|>


<commit_before>/* global -name*/
"use strict";

var mine = require("mine");
var pathJoin = require("pathjoin");
var binary = require('bodec');
var modes = require('js-git/lib/modes');
module.exports = amdTree;

function amdTree(servePath, req, callback) {

  var path = pathJoin(req.paths.root, req.input, req.paths.local);
  servePath(path, function (err, result) {
    if (err) return callback(err);
    return callback(null, {mode:result.mode,hash:result.hash,fetch:fetch});

    function fetch(callback) {
      result.fetch(function (err, value) {
        if (err) return callback(err);
        if (modes.isFile(result.mode)) {
          return compile(path, value, callback);
        }
        if (result.mode === modes.tree) {
          var tree = {};
          Object.keys(value).forEach(function (key) {
            var entry = value[key];
            if ((/\.js$/i.test(key) && modes.isFile(entry.mode)) ||
                entry.mode === modes.tree) {
              entry.hash += "-amd";
              tree[key] = entry;
            }
          });
          value = tree;
        }
        return callback(null, value);
      });
    }
  });

  function compile(path, blob, callback) {
    var prefix = pathJoin(req.paths.rule, "..", req.base || ".");
    var js = binary.toUnicode(blob);
    var deps = mine(js);
    var length = deps.length;
    var paths = new Array(length);
    var full = req.paths.full;
    var localPath = prefix ? full.substring(prefix.length + 1) : full;
    var base = localPath.substring(0, localPath.lastIndexOf("/"));

    for (var i = length - 1; i >= 0; i--) {
      var dep = deps[i];
      var depPath = dep.name[0] === "." ? pathJoin(base, dep.name) : dep.name;
      if (!(/\.[^\/]+$/.test(depPath))) depPath += ".js";
      paths[i] = depPath;
      js = js.substr(0, dep.offset) + depPath + js.substr(dep.offset + dep.name.length);
    }
    js = "define(" + JSON.stringify(localPath) + ", " +
        JSON.stringify(paths) + ", function (module, exports) { " +
        js + "\n});\n";
    callback(null, binary.fromUnicode(js));
  }
}<commit_msg>Make amd-tree ignore some files<commit_after>/* global -name*/
"use strict";

var mine = require("mine");
var pathJoin = require("pathjoin");
var binary = require('bodec');
var modes = require('js-git/lib/modes');
module.exports = amdTree;

function amdTree(servePath, req, callback) {

  var path = pathJoin(req.paths.root, req.input, req.paths.local);
  servePath(path, function (err, result) {
    if (err) return callback(err);
    if (!shouldHandle(req.paths.rule), result) return callback();
    return callback(null, {
      mode: result.mode,
      hash: result.hash + "-amd",
      root: result.root,
      fetch : fetch
    });

    function fetch(callback) {
      result.fetch(function (err, value) {
        if (err) return callback(err);
        if (modes.isFile(result.mode)) {
          return compile(path, value, callback);
        }
        if (result.mode === modes.tree) {
          var tree = {};
          Object.keys(value).forEach(function (key) {
            var entry = value[key];
            if (shouldHandle(key, entry)) {
              entry.hash += "-amd";
              tree[key] = entry;
            }
          });
          value = tree;
        }
        return callback(null, value);
      });
    }
  });

  function shouldHandle(path, entry) {
    return entry && (entry.mode === modes.tree ||
           modes.isFile(entry.mode) && /\.js$/i.test(path));
  }

  function compile(path, blob, callback) {
    var prefix = pathJoin(req.paths.rule, "..", req.base || ".");
    var js = binary.toUnicode(blob);
    var deps = mine(js);
    var length = deps.length;
    var paths = new Array(length);
    var full = req.paths.full;
    var localPath = prefix ? full.substring(prefix.length + 1) : full;
    var base = localPath.substring(0, localPath.lastIndexOf("/"));

    for (var i = length - 1; i >= 0; i--) {
      var dep = deps[i];
      var depPath = dep.name[0] === "." ? pathJoin(base, dep.name) : dep.name;
      if (!(/\.[^\/]+$/.test(depPath))) depPath += ".js";
      paths[i] = depPath;
      js = js.substr(0, dep.offset) + depPath + js.substr(dep.offset + dep.name.length);
    }
    js = "define(" + JSON.stringify(localPath) + ", " +
        JSON.stringify(paths) + ", function (module, exports) { " +
        js + "\n});\n";
    callback(null, binary.fromUnicode(js));
  }
}<|endoftext|>


<commit_before>import {compareTranspiled} from "./helpers";

describe('require', () => {
	test('should change relative path', () => {
		const options = {
			redirect: {
				"/examples(/\\w+)*/lib\\.js$" : "./examples/require/different/lib"
			}
		};

		return compareTranspiled("examples/require/relative.js", options);
	});

	test('should change relative path dynamically', () => {
		const options = {
			redirect: {
				"/examples(?:/\\w+)*/(\\w+)\\.js$" : "./examples/require/different/$1"
			}
		};

		return compareTranspiled("examples/require/relative.js", options);
	});

	test('should change path from a node module to relative', () => {
		const options = {
			redirect: {
				"node_module" : "./examples/require/different/lib.js"
			}
		};

		return compareTranspiled("examples/require/module.js", options);
	});

	test('should dynamically change path from a node module to relative', () => {
		const options = {
			redirect: {
				"node_module/(\\w+)\\.js$" : "./examples/require/different/$1"
			}
		};

		return compareTranspiled("examples/require/moduleRelative.js", options);
	});

	test('should change path from a node module to a different node module', () => {
		const options = {
			redirect: {
				"node_module" : "different_node_module"
			},
			root: "./examples"
		};

		return compareTranspiled("examples/require/module2module.js", options);
	});

	test('should dynamically change path from a node module to a different node module', () => {
		const options = {
			redirect: {
				"node_module/([\\w.]+)$" : "different_node_module/$1"
			},
			root: "./examples"
		};

		return compareTranspiled("examples/require/module2moduleRelative.js", options);
	});
});<commit_msg>test(require): Add test for relative to node change<commit_after>import {compareTranspiled} from "./helpers";

describe('require', () => {
	test('should change relative path', () => {
		const options = {
			redirect: {
				"/examples(/\\w+)*/lib\\.js$" : "./examples/require/different/lib"
			}
		};

		return compareTranspiled("examples/require/relative.js", options);
	});

	test('should change relative path dynamically', () => {
		const options = {
			redirect: {
				"/examples(?:/\\w+)*/(\\w+)\\.js$" : "./examples/require/different/$1"
			}
		};

		return compareTranspiled("examples/require/relative.js", options);
	});

	test('should change path from a node module to relative', () => {
		const options = {
			redirect: {
				"node_module" : "./examples/require/different/lib.js"
			}
		};

		return compareTranspiled("examples/require/module.js", options);
	});

	test('should dynamically change path from a node module to relative', () => {
		const options = {
			redirect: {
				"node_module/(\\w+)\\.js$" : "./examples/require/different/$1"
			}
		};

		return compareTranspiled("examples/require/moduleRelative.js", options);
	});

	test('should change path from a node module to a different node module', () => {
		const options = {
			redirect: {
				"node_module" : "different_node_module"
			},
			root: "./examples"
		};

		return compareTranspiled("examples/require/module2module.js", options);
	});

	test('should dynamically change path from a node module to a different node module', () => {
		const options = {
			redirect: {
				"node_module/([\\w.]+)$" : "different_node_module/$1"
			},
			root: "./examples"
		};

		return compareTranspiled("examples/require/module2moduleRelative.js", options);
	});

	test('should dynamically change path from relative to a node module', () => {
		const options = {
			redirect: {
				"/examples(/\\w+)*/lib\\.js$" : "node_module"
			},
			root: "./examples"
		};

		return compareTranspiled("examples/require/relative2module.js", options);
	});
});<|endoftext|>


<commit_before>/*!
 * clipboard.js v2.0.0
 * https://zenorocha.github.io/clipboard.js
 *
 * Licensed MIT © Zeno Rocha
 */
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(t){function e(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,e),r.l=!0,r.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,o){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:o})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=3)}([function(t,e,n){var o,r,i;!function(a,c){r=[t,n(7)],o=c,void 0!==(i="function"==typeof o?o.apply(e,r):o)&&(t.exports=i)}(0,function(t,e){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}var o=function(t){return t&&t.__esModule?t:{default:t}}(e),r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function t(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}return function(e,n,o){return n&&t(e.prototype,n),o&&t(e,o),e}}(),a=function(){function t(e){n(this,t),this.resolveOptions(e),this.initSelection()}return i(t,[{key:"resolveOptions",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.container=t.container,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var n=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=n+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=(0,o.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=(0,o.default)(this.target),this.copyText()}},{key:"copyText",value:function(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==(void 0===t?"undefined":r(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),t}();t.exports=a})},function(t,e,n){function o(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!c.string(e))throw new TypeError("Second argument must be a String");if(!c.fn(n))throw new TypeError("Third argument must be a Function");if(c.node(t))return r(t,e,n);if(c.nodeList(t))return i(t,e,n);if(c.string(t))return a(t,e,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function r(t,e,n){return t.addEventListener(e,n),{destroy:function(){t.removeEventListener(e,n)}}}function i(t,e,n){return Array.prototype.forEach.call(t,function(t){t.addEventListener(e,n)}),{destroy:function(){Array.prototype.forEach.call(t,function(t){t.removeEventListener(e,n)})}}}function a(t,e,n){return u(document.body,t,e,n)}var c=n(6),u=n(5);t.exports=o},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){function o(){r.off(t,o),e.apply(n,arguments)}var r=this;return o._=e,this.on(t,o,n)},emit:function(t){var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;for(o;o<r;o++)n[o].fn.apply(n[o].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),o=n[t],r=[];if(o&&e)for(var i=0,a=o.length;i<a;i++)o[i].fn!==e&&o[i].fn._!==e&&r.push(o[i]);return r.length?n[t]=r:delete n[t],this}},t.exports=n},function(t,e,n){var o,r,i;!function(a,c){r=[t,n(0),n(2),n(1)],o=c,void 0!==(i="function"==typeof o?o.apply(e,r):o)&&(t.exports=i)}(0,function(t,e,n,o){"use strict";function r(t){return t&&t.__esModule?t:{default:t}}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function a(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function c(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function u(t,e){var n="data-clipboard-"+t;if(e.hasAttribute(n))return e.getAttribute(n)}var l=r(e),s=r(n),f=r(o),d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},h=function(){function t(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}return function(e,n,o){return n&&t(e.prototype,n),o&&t(e,o),e}}(),p=function(t){function e(t,n){i(this,e);var o=a(this,(e.__proto__||Object.getPrototypeOf(e)).call(this));return o.resolveOptions(n),o.listenClick(t),o}return c(e,t),h(e,[{key:"resolveOptions",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===d(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=(0,f.default)(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new l.default({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return u("action",t)}},{key:"defaultTarget",value:function(t){var e=u("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return u("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],e="string"==typeof t?[t]:t,n=!!document.queryCommandSupported;return e.forEach(function(t){n=n&&!!document.queryCommandSupported(t)}),n}}]),e}(s.default);t.exports=p})},function(t,e){function n(t,e){for(;t&&t.nodeType!==o;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}var o=9;if("undefined"!=typeof Element&&!Element.prototype.matches){var r=Element.prototype;r.matches=r.matchesSelector||r.mozMatchesSelector||r.msMatchesSelector||r.oMatchesSelector||r.webkitMatchesSelector}t.exports=n},function(t,e,n){function o(t,e,n,o,r){var a=i.apply(this,arguments);return t.addEventListener(n,a,r),{destroy:function(){t.removeEventListener(n,a,r)}}}function r(t,e,n,r,i){return"function"==typeof t.addEventListener?o.apply(null,arguments):"function"==typeof n?o.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return o(t,e,n,r,i)}))}function i(t,e,n,o){return function(n){n.delegateTarget=a(n.target,e),n.delegateTarget&&o.call(t,n)}}var a=n(4);t.exports=r},function(t,e){e.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},e.nodeList=function(t){var n=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===n||"[object HTMLCollection]"===n)&&"length"in t&&(0===t.length||e.node(t[0]))},e.string=function(t){return"string"==typeof t||t instanceof String},e.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},function(t,e){function n(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}t.exports=n}])});<commit_msg>Update clipboard.js to v2.0.3<commit_after>/*!
 * clipboard.js v2.0.3
 * https://zenorocha.github.io/clipboard.js
 *
 * Licensed MIT © Zeno Rocha
 */
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(window,function(){return function(n){var o={};function r(t){if(o[t])return o[t].exports;var e=o[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,r),e.l=!0,e.exports}return r.m=n,r.c=o,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,n){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function o(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}return function(t,e,n){return e&&o(t.prototype,e),n&&o(t,n),t}}(),a=o(n(1)),c=o(n(3)),u=o(n(4));function o(t){return t&&t.__esModule?t:{default:t}}var l=function(t){function o(t,e){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,o);var n=function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}(this,(o.__proto__||Object.getPrototypeOf(o)).call(this));return n.resolveOptions(e),n.listenClick(t),n}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(o,c.default),i(o,[{key:"resolveOptions",value:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===r(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=(0,u.default)(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new a.default({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return s("action",t)}},{key:"defaultTarget",value:function(t){var e=s("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return s("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:["copy","cut"],e="string"==typeof t?[t]:t,n=!!document.queryCommandSupported;return e.forEach(function(t){n=n&&!!document.queryCommandSupported(t)}),n}}]),o}();function s(t,e){var n="data-clipboard-"+t;if(e.hasAttribute(n))return e.getAttribute(n)}t.exports=l},function(t,e,n){"use strict";var o,r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function o(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}return function(t,e,n){return e&&o(t.prototype,e),n&&o(t,n),t}}(),a=n(2),c=(o=a)&&o.__esModule?o:{default:o};var u=function(){function e(t){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,e),this.resolveOptions(t),this.initSelection()}return i(e,[{key:"resolveOptions",value:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.container=t.container,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var n=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=n+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=(0,c.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=(0,c.default)(this.target),this.copyText()}},{key:"copyText",value:function(){var e=void 0;try{e=document.execCommand(this.action)}catch(t){e=!1}this.handleResult(e)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==(void 0===t?"undefined":r(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),e}();t.exports=u},function(t,e){t.exports=function(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){var o=this;function r(){o.off(t,r),e.apply(n,arguments)}return r._=e,this.on(t,r,n)},emit:function(t){for(var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;o<r;o++)n[o].fn.apply(n[o].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),o=n[t],r=[];if(o&&e)for(var i=0,a=o.length;i<a;i++)o[i].fn!==e&&o[i].fn._!==e&&r.push(o[i]);return r.length?n[t]=r:delete n[t],this}},t.exports=n},function(t,e,n){var d=n(5),h=n(6);t.exports=function(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!d.string(e))throw new TypeError("Second argument must be a String");if(!d.fn(n))throw new TypeError("Third argument must be a Function");if(d.node(t))return s=e,f=n,(l=t).addEventListener(s,f),{destroy:function(){l.removeEventListener(s,f)}};if(d.nodeList(t))return a=t,c=e,u=n,Array.prototype.forEach.call(a,function(t){t.addEventListener(c,u)}),{destroy:function(){Array.prototype.forEach.call(a,function(t){t.removeEventListener(c,u)})}};if(d.string(t))return o=t,r=e,i=n,h(document.body,o,r,i);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList");var o,r,i,a,c,u,l,s,f}},function(t,n){n.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},n.nodeList=function(t){var e=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===e||"[object HTMLCollection]"===e)&&"length"in t&&(0===t.length||n.node(t[0]))},n.string=function(t){return"string"==typeof t||t instanceof String},n.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},function(t,e,n){var a=n(7);function i(t,e,n,o,r){var i=function(e,n,t,o){return function(t){t.delegateTarget=a(t.target,n),t.delegateTarget&&o.call(e,t)}}.apply(this,arguments);return t.addEventListener(n,i,r),{destroy:function(){t.removeEventListener(n,i,r)}}}t.exports=function(t,e,n,o,r){return"function"==typeof t.addEventListener?i.apply(null,arguments):"function"==typeof n?i.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return i(t,e,n,o,r)}))}},function(t,e){if("undefined"!=typeof Element&&!Element.prototype.matches){var n=Element.prototype;n.matches=n.matchesSelector||n.mozMatchesSelector||n.msMatchesSelector||n.oMatchesSelector||n.webkitMatchesSelector}t.exports=function(t,e){for(;t&&9!==t.nodeType;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}}])});
<|endoftext|>


<commit_before>
// @require core/colors/colors.js
// @require core/widget/widget.js
// @require widgets/remote/loader/loader.js

//TODO: Add a demo for it

(function ( $, _, Svelto, Widgets, Factory, Colors ) {

  /* CONFIG */

  let config = {
    name: 'chatMessageReplyable',
    plugin: true,
    selector: '.chat-message.replyable',
    templates: {
      reply: _.template ( `
        <div class="chat-message-reply no-tip <%= o.cls %>">
          <%= o.img ? '

' : '' %>
          <div class="chat-message-content remote-loader no-wrap container bordered" data-url="<%= o.url %>">
            <svg class="spinner">
              <circle cx="1.625em" cy="1.625em" r="1.25em"></circle>
            </svg>

      ` )
    },
    options: {
      url: false, // Url for remote-loading the actual reply widget
      datas: {
        url: 'reply-url'
      },
      selectors: {
        reply: '+ .chat-message-reply'
      },
      callbacks: {
        reply: _.noop,
        unreply: _.noop
      }
    }
  };

  /* CHAT MESSAGE REPLY */

  class ChatMessageReplyable extends Widgets.Widget {

    /* SPECIAL */

    _variables () {

      this.$replyable = this.$element;

      this.options.url = this.$replyable.data ( this.options.datas.url ) || this.options.url;

    }

    /* HELPERS */

    _getReply () {

      return this.$replyable.find ( this.options.selectors.reply );

    }

    /* API */

    reply () {

      let $reply = this._getReply ();

      if ( $reply.length ) return $reply.trigger ( 'focus' );

      let cls = this.$replyable.attr ( 'class' ),
          img = !!this.$replyable.children ( '.chat-message-img' ).length,
          url = this.options.url,
          options = {cls, img, url};

      $reply = $(this._template ( 'reply', options ));

      this.$replyable.after ( $reply );

      $reply.trigger ( 'focus' ).widgetize ();

      this._trigger ( 'reply' );

    }

    unreply () {

      let $reply = this._getReply ();

      if ( !$reply.length ) return;

      $reply.trigger ( 'chatmessagereplyablereply:unreply' ).remove ();

      this._trigger ( 'unreply' );

    }

  }

  /* FACTORY */

  Factory.make ( ChatMessageReplyable, config );

}( Svelto.$, Svelto._, Svelto, Svelto.Widgets, Svelto.Factory, Svelto.Colors ));
<commit_msg>ChatMessageReplyable: avoiding using weird selectors<commit_after>
// @require core/colors/colors.js
// @require core/widget/widget.js
// @require widgets/remote/loader/loader.js

//TODO: Add a demo for it

(function ( $, _, Svelto, Widgets, Factory, Colors ) {

  /* CONFIG */

  let config = {
    name: 'chatMessageReplyable',
    plugin: true,
    selector: '.chat-message.replyable',
    templates: {
      reply: _.template ( `
        <div class="chat-message-reply no-tip <%= o.cls %>">
          <%= o.img ? '

' : '' %>
          <div class="chat-message-content remote-loader no-wrap container bordered" data-url="<%= o.url %>">
            <svg class="spinner">
              <circle cx="1.625em" cy="1.625em" r="1.25em"></circle>
            </svg>

      ` )
    },
    options: {
      url: false, // Url for remote-loading the actual reply widget
      datas: {
        url: 'reply-url'
      },
      selectors: {
        // reply: '+ .chat-message-reply' //TODO: Limited DOM libraries support
      },
      callbacks: {
        reply: _.noop,
        unreply: _.noop
      }
    }
  };

  /* CHAT MESSAGE REPLY */

  class ChatMessageReplyable extends Widgets.Widget {

    /* SPECIAL */

    _variables () {

      this.$replyable = this.$element;

      this.options.url = this.$replyable.data ( this.options.datas.url ) || this.options.url;

    }

    /* HELPERS */

    _getReply () {

      return this.$replyable.next ().filter ( '.chat-message-reply' ); //TODO: Test me

    }

    /* API */

    reply () {

      let $reply = this._getReply ();

      if ( $reply.length ) return $reply.trigger ( 'focus' );

      let cls = this.$replyable.attr ( 'class' ),
          img = !!this.$replyable.children ( '.chat-message-img' ).length,
          url = this.options.url,
          options = {cls, img, url};

      $reply = $(this._template ( 'reply', options ));

      this.$replyable.after ( $reply );

      $reply.trigger ( 'focus' ).widgetize ();

      this._trigger ( 'reply' );

    }

    unreply () {

      let $reply = this._getReply ();

      if ( !$reply.length ) return;

      $reply.trigger ( 'chatmessagereplyablereply:unreply' ).remove ();

      this._trigger ( 'unreply' );

    }

  }

  /* FACTORY */

  Factory.make ( ChatMessageReplyable, config );

}( Svelto.$, Svelto._, Svelto, Svelto.Widgets, Svelto.Factory, Svelto.Colors ));
<|endoftext|>


<commit_before>// To keep the UI snappy, we run WebTorrent in its own hidden window, a separate
// process from the main window.
console.time('init')

const crypto = require('crypto')
const deepEqual = require('deep-equal')
const defaultAnnounceList = require('create-torrent').announceList
const electron = require('electron')
const fs = require('fs')
const mkdirp = require('mkdirp')
const mm = require('music-metadata')
const networkAddress = require('network-address')
const path = require('path')
const WebTorrent = require('webtorrent')
const zeroFill = require('zero-fill')

const crashReporter = require('../crash-reporter')
const config = require('../config')
const {TorrentKeyNotFoundError} = require('./lib/errors')
const torrentPoster = require('./lib/torrent-poster')

// Report when the process crashes
crashReporter.init()

// Send & receive messages from the main window
const ipc = electron.ipcRenderer

// Force use of webtorrent trackers on all torrents
global.WEBTORRENT_ANNOUNCE = defaultAnnounceList
  .map((arr) => arr[0])
  .filter((url) => url.indexOf('wss://') === 0 || url.indexOf('ws://') === 0)

/**
 * WebTorrent version.
 */
const VERSION = require('../../package.json').version

/**
 * Version number in Azureus-style. Generated from major and minor semver version.
 * For example:
 *   '0.16.1' -> '0016'
 *   '1.2.5' -> '0102'
 */
const VERSION_STR = VERSION.match(/([0-9]+)/g)
  .slice(0, 2)
  .map((v) => zeroFill(2, v))
  .join('')

/**
 * Version prefix string (used in peer ID). WebTorrent uses the Azureus-style
 * encoding: '-', two characters for client id ('WW'), four ascii digits for version
 * number, '-', followed by random numbers.
 * For example:
 *   '-WW0102-'...
 */
const VERSION_PREFIX = '-WD' + VERSION_STR + '-'

/**
 * Generate an ephemeral peer ID each time.
 */
const PEER_ID = Buffer.from(VERSION_PREFIX + crypto.randomBytes(9).toString('base64'))

// Connect to the WebTorrent and BitTorrent networks. WebTorrent Desktop is a hybrid
// client, as explained here: https://webtorrent.io/faq
let client = window.client = new WebTorrent({ peerId: PEER_ID })

// WebTorrent-to-HTTP streaming sever
let server = null

// Used for diffing, so we only send progress updates when necessary
let prevProgress = null

init()

function init () {
  listenToClientEvents()

  ipc.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes, selections) =>
    startTorrenting(torrentKey, torrentID, path, fileModtimes, selections))
  ipc.on('wt-stop-torrenting', (e, infoHash) =>
    stopTorrenting(infoHash))
  ipc.on('wt-create-torrent', (e, torrentKey, options) =>
    createTorrent(torrentKey, options))
  ipc.on('wt-save-torrent-file', (e, torrentKey) =>
    saveTorrentFile(torrentKey))
  ipc.on('wt-generate-torrent-poster', (e, torrentKey) =>
    generateTorrentPoster(torrentKey))
  ipc.on('wt-get-audio-metadata', (e, infoHash, index) =>
    getAudioMetadata(infoHash, index))
  ipc.on('wt-start-server', (e, infoHash) =>
    startServer(infoHash))
  ipc.on('wt-stop-server', (e) =>
    stopServer())
  ipc.on('wt-select-files', (e, infoHash, selections) =>
    selectFiles(infoHash, selections))

  ipc.send('ipcReadyWebTorrent')

  window.addEventListener('error', (e) =>
    ipc.send('wt-uncaught-error', {message: e.error.message, stack: e.error.stack}),
  true)

  setInterval(updateTorrentProgress, 1000)
  console.timeEnd('init')
}

function listenToClientEvents () {
  client.on('warning', (err) => ipc.send('wt-warning', null, err.message))
  client.on('error', (err) => ipc.send('wt-error', null, err.message))
}

// Starts a given TorrentID, which can be an infohash, magnet URI, etc.
// Returns a WebTorrent object. See https://git.io/vik9M
function startTorrenting (torrentKey, torrentID, path, fileModtimes, selections) {
  console.log('starting torrent %s: %s', torrentKey, torrentID)

  const torrent = client.add(torrentID, {
    path: path,
    fileModtimes: fileModtimes
  })
  torrent.key = torrentKey

  // Listen for ready event, progress notifications, etc
  addTorrentEvents(torrent)

  // Only download the files the user wants, not necessarily all files
  torrent.once('ready', () => selectFiles(torrent, selections))
}

function stopTorrenting (infoHash) {
  console.log('--- STOP TORRENTING: ', infoHash)
  const torrent = client.get(infoHash)
  if (torrent) torrent.destroy()
}

// Create a new torrent, start seeding
function createTorrent (torrentKey, options) {
  console.log('creating torrent', torrentKey, options)
  const paths = options.files.map((f) => f.path)
  const torrent = client.seed(paths, options)
  torrent.key = torrentKey
  addTorrentEvents(torrent)
  ipc.send('wt-new-torrent')
}

function addTorrentEvents (torrent) {
  torrent.on('warning', (err) =>
    ipc.send('wt-warning', torrent.key, err.message))
  torrent.on('error', (err) =>
    ipc.send('wt-error', torrent.key, err.message))
  torrent.on('infoHash', () =>
    ipc.send('wt-infohash', torrent.key, torrent.infoHash))
  torrent.on('metadata', torrentMetadata)
  torrent.on('ready', torrentReady)
  torrent.on('done', torrentDone)

  function torrentMetadata () {
    const info = getTorrentInfo(torrent)
    ipc.send('wt-metadata', torrent.key, info)

    updateTorrentProgress()
  }

  function torrentReady () {
    const info = getTorrentInfo(torrent)
    ipc.send('wt-ready', torrent.key, info)
    ipc.send('wt-ready-' + torrent.infoHash, torrent.key, info)

    updateTorrentProgress()
  }

  function torrentDone () {
    const info = getTorrentInfo(torrent)
    ipc.send('wt-done', torrent.key, info)

    updateTorrentProgress()

    torrent.getFileModtimes(function (err, fileModtimes) {
      if (err) return onError(err)
      ipc.send('wt-file-modtimes', torrent.key, fileModtimes)
    })
  }
}

// Produces a JSON saveable summary of a torrent
function getTorrentInfo (torrent) {
  return {
    infoHash: torrent.infoHash,
    magnetURI: torrent.magnetURI,
    name: torrent.name,
    path: torrent.path,
    files: torrent.files.map(getTorrentFileInfo),
    bytesReceived: torrent.received
  }
}

// Produces a JSON saveable summary of a file in a torrent
function getTorrentFileInfo (file) {
  return {
    name: file.name,
    length: file.length,
    path: file.path
  }
}

// Every time we resolve a magnet URI, save the torrent file so that we can use
// it on next startup. Starting with the full torrent metadata will be faster
// than re-fetching it from peers using ut_metadata.
function saveTorrentFile (torrentKey) {
  const torrent = getTorrent(torrentKey)
  const torrentPath = path.join(config.TORRENT_PATH, torrent.infoHash + '.torrent')

  fs.access(torrentPath, fs.constants.R_OK, function (err) {
    const fileName = torrent.infoHash + '.torrent'
    if (!err) {
      // We've already saved the file
      return ipc.send('wt-file-saved', torrentKey, fileName)
    }

    // Otherwise, save the .torrent file, under the app config folder
    mkdirp(config.TORRENT_PATH, function (_) {
      fs.writeFile(torrentPath, torrent.torrentFile, function (err) {
        if (err) return console.log('error saving torrent file %s: %o', torrentPath, err)
        console.log('saved torrent file %s', torrentPath)
        return ipc.send('wt-file-saved', torrentKey, fileName)
      })
    })
  })
}

// Save a JPG that represents a torrent.
// Auto chooses either a frame from a video file, an image, etc
function generateTorrentPoster (torrentKey) {
  const torrent = getTorrent(torrentKey)
  torrentPoster(torrent, function (err, buf, extension) {
    if (err) return console.log('error generating poster: %o', err)
    // save it for next time
    mkdirp(config.POSTER_PATH, function (err) {
      if (err) return console.log('error creating poster dir: %o', err)
      const posterFileName = torrent.infoHash + extension
      const posterFilePath = path.join(config.POSTER_PATH, posterFileName)
      fs.writeFile(posterFilePath, buf, function (err) {
        if (err) return console.log('error saving poster: %o', err)
        // show the poster
        ipc.send('wt-poster', torrentKey, posterFileName)
      })
    })
  })
}

function updateTorrentProgress () {
  const progress = getTorrentProgress()
  // TODO: diff torrent-by-torrent, not once for the whole update
  if (prevProgress && deepEqual(progress, prevProgress, {strict: true})) {
    return /* don't send heavy object if it hasn't changed */
  }
  ipc.send('wt-progress', progress)
  prevProgress = progress
}

function getTorrentProgress () {
  // First, track overall progress
  const progress = client.progress
  const hasActiveTorrents = client.torrents.some(function (torrent) {
    return torrent.progress !== 1
  })

  // Track progress for every file in each torrent
  // TODO: ideally this would be tracked by WebTorrent, which could do it
  // more efficiently than looping over torrent.bitfield
  const torrentProg = client.torrents.map(function (torrent) {
    const fileProg = torrent.files && torrent.files.map(function (file, index) {
      const numPieces = file._endPiece - file._startPiece + 1
      let numPiecesPresent = 0
      for (let piece = file._startPiece; piece <= file._endPiece; piece++) {
        if (torrent.bitfield.get(piece)) numPiecesPresent++
      }
      return {
        startPiece: file._startPiece,
        endPiece: file._endPiece,
        numPieces,
        numPiecesPresent
      }
    })
    return {
      torrentKey: torrent.key,
      ready: torrent.ready,
      progress: torrent.progress,
      downloaded: torrent.downloaded,
      downloadSpeed: torrent.downloadSpeed,
      uploadSpeed: torrent.uploadSpeed,
      numPeers: torrent.numPeers,
      length: torrent.length,
      bitfield: torrent.bitfield,
      files: fileProg
    }
  })

  return {
    torrents: torrentProg,
    progress,
    hasActiveTorrents
  }
}

function startServer (infoHash) {
  const torrent = client.get(infoHash)
  if (torrent.ready) startServerFromReadyTorrent(torrent)
  else torrent.once('ready', () => startServerFromReadyTorrent(torrent))
}

function startServerFromReadyTorrent (torrent, cb) {
  if (server) return

  // start the streaming torrent-to-http server
  server = torrent.createServer()
  server.listen(0, function () {
    const port = server.address().port
    const urlSuffix = ':' + port
    const info = {
      torrentKey: torrent.key,
      localURL: 'http://localhost' + urlSuffix,
      networkURL: 'http://' + networkAddress() + urlSuffix
    }

    ipc.send('wt-server-running', info)
    ipc.send('wt-server-' + torrent.infoHash, info)
  })
}

function stopServer () {
  if (!server) return
  server.destroy()
  server = null
}

console.log('Initializing...')

function getAudioMetadata (infoHash, index) {
  const torrent = client.get(infoHash)
  const file = torrent.files[index]

  const options = {native: false, skipCovers: true}
  const onMetaData = file.done
    // If completed; use direct file access
    ? mm.parseFile(path.join(torrent.path, file.path), options)
    // otherwise stream
    : mm.parseStream(file.createReadStream(), file.name, options)

  onMetaData
    .then(function (metadata) {
      console.log('got audio metadata for %s: %o', file.name, metadata)
      ipc.send('wt-audio-metadata', infoHash, index, metadata)
    }).catch(function (err) {
      return console.log('error getting audio metadata for ' + infoHash + ':' + index, err)
    })
}

function selectFiles (torrentOrInfoHash, selections) {
  // Get the torrent object
  let torrent
  if (typeof torrentOrInfoHash === 'string') {
    torrent = client.get(torrentOrInfoHash)
  } else {
    torrent = torrentOrInfoHash
  }
  if (!torrent) {
    throw new Error('selectFiles: missing torrent ' + torrentOrInfoHash)
  }

  // Selections not specified?
  // Load all files. We still need to replace the default whole-torrent
  // selection with individual selections for each file, so we can
  // select/deselect files later on
  if (!selections) {
    selections = torrent.files.map((x) => true)
  }

  // Selections specified incorrectly?
  if (selections.length !== torrent.files.length) {
    throw new Error('got ' + selections.length + ' file selections, ' +
      'but the torrent contains ' + torrent.files.length + ' files')
  }

  // Remove default selection (whole torrent)
  torrent.deselect(0, torrent.pieces.length - 1, false)

  // Add selections (individual files)
  for (let i = 0; i < selections.length; i++) {
    const file = torrent.files[i]
    if (selections[i]) {
      file.select()
    } else {
      console.log('deselecting file ' + i + ' of torrent ' + torrent.name)
      file.deselect()
    }
  }
}

// Gets a WebTorrent handle by torrentKey
// Throws an Error if we're not currently torrenting anything w/ that key
function getTorrent (torrentKey) {
  const ret = client.torrents.find((x) => x.key === torrentKey)
  if (!ret) throw new TorrentKeyNotFoundError(torrentKey)
  return ret
}

function onError (err) {
  console.log(err)
}

// TODO: remove this once the following bugs are fixed:
// https://bugs.chromium.org/p/chromium/issues/detail?id=490143
// https://github.com/electron/electron/issues/7212
window.testOfflineMode = function () {
  console.log('Test, going OFFLINE')
  client = window.client = new WebTorrent({
    peerId: PEER_ID,
    tracker: false,
    dht: false,
    webSeeds: false
  })
  listenToClientEvents()
}
<commit_msg>Pass file length parameter to music-metadata parser. In some cases this is required to calculate the duration.<commit_after>// To keep the UI snappy, we run WebTorrent in its own hidden window, a separate
// process from the main window.
console.time('init')

const crypto = require('crypto')
const deepEqual = require('deep-equal')
const defaultAnnounceList = require('create-torrent').announceList
const electron = require('electron')
const fs = require('fs')
const mkdirp = require('mkdirp')
const mm = require('music-metadata')
const networkAddress = require('network-address')
const path = require('path')
const WebTorrent = require('webtorrent')
const zeroFill = require('zero-fill')

const crashReporter = require('../crash-reporter')
const config = require('../config')
const {TorrentKeyNotFoundError} = require('./lib/errors')
const torrentPoster = require('./lib/torrent-poster')

// Report when the process crashes
crashReporter.init()

// Send & receive messages from the main window
const ipc = electron.ipcRenderer

// Force use of webtorrent trackers on all torrents
global.WEBTORRENT_ANNOUNCE = defaultAnnounceList
  .map((arr) => arr[0])
  .filter((url) => url.indexOf('wss://') === 0 || url.indexOf('ws://') === 0)

/**
 * WebTorrent version.
 */
const VERSION = require('../../package.json').version

/**
 * Version number in Azureus-style. Generated from major and minor semver version.
 * For example:
 *   '0.16.1' -> '0016'
 *   '1.2.5' -> '0102'
 */
const VERSION_STR = VERSION.match(/([0-9]+)/g)
  .slice(0, 2)
  .map((v) => zeroFill(2, v))
  .join('')

/**
 * Version prefix string (used in peer ID). WebTorrent uses the Azureus-style
 * encoding: '-', two characters for client id ('WW'), four ascii digits for version
 * number, '-', followed by random numbers.
 * For example:
 *   '-WW0102-'...
 */
const VERSION_PREFIX = '-WD' + VERSION_STR + '-'

/**
 * Generate an ephemeral peer ID each time.
 */
const PEER_ID = Buffer.from(VERSION_PREFIX + crypto.randomBytes(9).toString('base64'))

// Connect to the WebTorrent and BitTorrent networks. WebTorrent Desktop is a hybrid
// client, as explained here: https://webtorrent.io/faq
let client = window.client = new WebTorrent({ peerId: PEER_ID })

// WebTorrent-to-HTTP streaming sever
let server = null

// Used for diffing, so we only send progress updates when necessary
let prevProgress = null

init()

function init () {
  listenToClientEvents()

  ipc.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes, selections) =>
    startTorrenting(torrentKey, torrentID, path, fileModtimes, selections))
  ipc.on('wt-stop-torrenting', (e, infoHash) =>
    stopTorrenting(infoHash))
  ipc.on('wt-create-torrent', (e, torrentKey, options) =>
    createTorrent(torrentKey, options))
  ipc.on('wt-save-torrent-file', (e, torrentKey) =>
    saveTorrentFile(torrentKey))
  ipc.on('wt-generate-torrent-poster', (e, torrentKey) =>
    generateTorrentPoster(torrentKey))
  ipc.on('wt-get-audio-metadata', (e, infoHash, index) =>
    getAudioMetadata(infoHash, index))
  ipc.on('wt-start-server', (e, infoHash) =>
    startServer(infoHash))
  ipc.on('wt-stop-server', (e) =>
    stopServer())
  ipc.on('wt-select-files', (e, infoHash, selections) =>
    selectFiles(infoHash, selections))

  ipc.send('ipcReadyWebTorrent')

  window.addEventListener('error', (e) =>
    ipc.send('wt-uncaught-error', {message: e.error.message, stack: e.error.stack}),
  true)

  setInterval(updateTorrentProgress, 1000)
  console.timeEnd('init')
}

function listenToClientEvents () {
  client.on('warning', (err) => ipc.send('wt-warning', null, err.message))
  client.on('error', (err) => ipc.send('wt-error', null, err.message))
}

// Starts a given TorrentID, which can be an infohash, magnet URI, etc.
// Returns a WebTorrent object. See https://git.io/vik9M
function startTorrenting (torrentKey, torrentID, path, fileModtimes, selections) {
  console.log('starting torrent %s: %s', torrentKey, torrentID)

  const torrent = client.add(torrentID, {
    path: path,
    fileModtimes: fileModtimes
  })
  torrent.key = torrentKey

  // Listen for ready event, progress notifications, etc
  addTorrentEvents(torrent)

  // Only download the files the user wants, not necessarily all files
  torrent.once('ready', () => selectFiles(torrent, selections))
}

function stopTorrenting (infoHash) {
  console.log('--- STOP TORRENTING: ', infoHash)
  const torrent = client.get(infoHash)
  if (torrent) torrent.destroy()
}

// Create a new torrent, start seeding
function createTorrent (torrentKey, options) {
  console.log('creating torrent', torrentKey, options)
  const paths = options.files.map((f) => f.path)
  const torrent = client.seed(paths, options)
  torrent.key = torrentKey
  addTorrentEvents(torrent)
  ipc.send('wt-new-torrent')
}

function addTorrentEvents (torrent) {
  torrent.on('warning', (err) =>
    ipc.send('wt-warning', torrent.key, err.message))
  torrent.on('error', (err) =>
    ipc.send('wt-error', torrent.key, err.message))
  torrent.on('infoHash', () =>
    ipc.send('wt-infohash', torrent.key, torrent.infoHash))
  torrent.on('metadata', torrentMetadata)
  torrent.on('ready', torrentReady)
  torrent.on('done', torrentDone)

  function torrentMetadata () {
    const info = getTorrentInfo(torrent)
    ipc.send('wt-metadata', torrent.key, info)

    updateTorrentProgress()
  }

  function torrentReady () {
    const info = getTorrentInfo(torrent)
    ipc.send('wt-ready', torrent.key, info)
    ipc.send('wt-ready-' + torrent.infoHash, torrent.key, info)

    updateTorrentProgress()
  }

  function torrentDone () {
    const info = getTorrentInfo(torrent)
    ipc.send('wt-done', torrent.key, info)

    updateTorrentProgress()

    torrent.getFileModtimes(function (err, fileModtimes) {
      if (err) return onError(err)
      ipc.send('wt-file-modtimes', torrent.key, fileModtimes)
    })
  }
}

// Produces a JSON saveable summary of a torrent
function getTorrentInfo (torrent) {
  return {
    infoHash: torrent.infoHash,
    magnetURI: torrent.magnetURI,
    name: torrent.name,
    path: torrent.path,
    files: torrent.files.map(getTorrentFileInfo),
    bytesReceived: torrent.received
  }
}

// Produces a JSON saveable summary of a file in a torrent
function getTorrentFileInfo (file) {
  return {
    name: file.name,
    length: file.length,
    path: file.path
  }
}

// Every time we resolve a magnet URI, save the torrent file so that we can use
// it on next startup. Starting with the full torrent metadata will be faster
// than re-fetching it from peers using ut_metadata.
function saveTorrentFile (torrentKey) {
  const torrent = getTorrent(torrentKey)
  const torrentPath = path.join(config.TORRENT_PATH, torrent.infoHash + '.torrent')

  fs.access(torrentPath, fs.constants.R_OK, function (err) {
    const fileName = torrent.infoHash + '.torrent'
    if (!err) {
      // We've already saved the file
      return ipc.send('wt-file-saved', torrentKey, fileName)
    }

    // Otherwise, save the .torrent file, under the app config folder
    mkdirp(config.TORRENT_PATH, function (_) {
      fs.writeFile(torrentPath, torrent.torrentFile, function (err) {
        if (err) return console.log('error saving torrent file %s: %o', torrentPath, err)
        console.log('saved torrent file %s', torrentPath)
        return ipc.send('wt-file-saved', torrentKey, fileName)
      })
    })
  })
}

// Save a JPG that represents a torrent.
// Auto chooses either a frame from a video file, an image, etc
function generateTorrentPoster (torrentKey) {
  const torrent = getTorrent(torrentKey)
  torrentPoster(torrent, function (err, buf, extension) {
    if (err) return console.log('error generating poster: %o', err)
    // save it for next time
    mkdirp(config.POSTER_PATH, function (err) {
      if (err) return console.log('error creating poster dir: %o', err)
      const posterFileName = torrent.infoHash + extension
      const posterFilePath = path.join(config.POSTER_PATH, posterFileName)
      fs.writeFile(posterFilePath, buf, function (err) {
        if (err) return console.log('error saving poster: %o', err)
        // show the poster
        ipc.send('wt-poster', torrentKey, posterFileName)
      })
    })
  })
}

function updateTorrentProgress () {
  const progress = getTorrentProgress()
  // TODO: diff torrent-by-torrent, not once for the whole update
  if (prevProgress && deepEqual(progress, prevProgress, {strict: true})) {
    return /* don't send heavy object if it hasn't changed */
  }
  ipc.send('wt-progress', progress)
  prevProgress = progress
}

function getTorrentProgress () {
  // First, track overall progress
  const progress = client.progress
  const hasActiveTorrents = client.torrents.some(function (torrent) {
    return torrent.progress !== 1
  })

  // Track progress for every file in each torrent
  // TODO: ideally this would be tracked by WebTorrent, which could do it
  // more efficiently than looping over torrent.bitfield
  const torrentProg = client.torrents.map(function (torrent) {
    const fileProg = torrent.files && torrent.files.map(function (file, index) {
      const numPieces = file._endPiece - file._startPiece + 1
      let numPiecesPresent = 0
      for (let piece = file._startPiece; piece <= file._endPiece; piece++) {
        if (torrent.bitfield.get(piece)) numPiecesPresent++
      }
      return {
        startPiece: file._startPiece,
        endPiece: file._endPiece,
        numPieces,
        numPiecesPresent
      }
    })
    return {
      torrentKey: torrent.key,
      ready: torrent.ready,
      progress: torrent.progress,
      downloaded: torrent.downloaded,
      downloadSpeed: torrent.downloadSpeed,
      uploadSpeed: torrent.uploadSpeed,
      numPeers: torrent.numPeers,
      length: torrent.length,
      bitfield: torrent.bitfield,
      files: fileProg
    }
  })

  return {
    torrents: torrentProg,
    progress,
    hasActiveTorrents
  }
}

function startServer (infoHash) {
  const torrent = client.get(infoHash)
  if (torrent.ready) startServerFromReadyTorrent(torrent)
  else torrent.once('ready', () => startServerFromReadyTorrent(torrent))
}

function startServerFromReadyTorrent (torrent, cb) {
  if (server) return

  // start the streaming torrent-to-http server
  server = torrent.createServer()
  server.listen(0, function () {
    const port = server.address().port
    const urlSuffix = ':' + port
    const info = {
      torrentKey: torrent.key,
      localURL: 'http://localhost' + urlSuffix,
      networkURL: 'http://' + networkAddress() + urlSuffix
    }

    ipc.send('wt-server-running', info)
    ipc.send('wt-server-' + torrent.infoHash, info)
  })
}

function stopServer () {
  if (!server) return
  server.destroy()
  server = null
}

console.log('Initializing...')

function getAudioMetadata (infoHash, index) {
  const torrent = client.get(infoHash)
  const file = torrent.files[index]

  const options = {native: false, skipCovers: true, fileSize: file.length}
  const onMetaData = file.done
    // If completed; use direct file access
    ? mm.parseFile(path.join(torrent.path, file.path), options)
    // otherwise stream
    : mm.parseStream(file.createReadStream(), file.name, options)

  onMetaData
    .then(function (metadata) {
      console.log('got audio metadata for %s (length=%s): %o', file.name, file.length, metadata)
      ipc.send('wt-audio-metadata', infoHash, index, metadata)
    }).catch(function (err) {
      return console.log('error getting audio metadata for ' + infoHash + ':' + index, err)
    })
}

function selectFiles (torrentOrInfoHash, selections) {
  // Get the torrent object
  let torrent
  if (typeof torrentOrInfoHash === 'string') {
    torrent = client.get(torrentOrInfoHash)
  } else {
    torrent = torrentOrInfoHash
  }
  if (!torrent) {
    throw new Error('selectFiles: missing torrent ' + torrentOrInfoHash)
  }

  // Selections not specified?
  // Load all files. We still need to replace the default whole-torrent
  // selection with individual selections for each file, so we can
  // select/deselect files later on
  if (!selections) {
    selections = torrent.files.map((x) => true)
  }

  // Selections specified incorrectly?
  if (selections.length !== torrent.files.length) {
    throw new Error('got ' + selections.length + ' file selections, ' +
      'but the torrent contains ' + torrent.files.length + ' files')
  }

  // Remove default selection (whole torrent)
  torrent.deselect(0, torrent.pieces.length - 1, false)

  // Add selections (individual files)
  for (let i = 0; i < selections.length; i++) {
    const file = torrent.files[i]
    if (selections[i]) {
      file.select()
    } else {
      console.log('deselecting file ' + i + ' of torrent ' + torrent.name)
      file.deselect()
    }
  }
}

// Gets a WebTorrent handle by torrentKey
// Throws an Error if we're not currently torrenting anything w/ that key
function getTorrent (torrentKey) {
  const ret = client.torrents.find((x) => x.key === torrentKey)
  if (!ret) throw new TorrentKeyNotFoundError(torrentKey)
  return ret
}

function onError (err) {
  console.log(err)
}

// TODO: remove this once the following bugs are fixed:
// https://bugs.chromium.org/p/chromium/issues/detail?id=490143
// https://github.com/electron/electron/issues/7212
window.testOfflineMode = function () {
  console.log('Test, going OFFLINE')
  client = window.client = new WebTorrent({
    peerId: PEER_ID,
    tracker: false,
    dht: false,
    webSeeds: false
  })
  listenToClientEvents()
}
<|endoftext|>


<commit_before>var quicktests = (quicktests || []);

var quicktest = function(svg, data, Plottable){

        var dataseries = data[0].slice(0, 21);
        var dataseries_top = data[1].slice(0, 21);
        for (var i = 0; i < 20; ++i) {
          dataseries_top[i].x = dataseries[i].x;
          dataseries_top[i].y += dataseries[i].y;
        }

        var xScale = new Plottable.Scale.Linear();
        var xAxis = new Plottable.Axis.XAxis(xScale, "bottom");

        var yScale = new Plottable.Scale.Linear();
        var yAxis = new Plottable.Axis.YAxis(yScale, "left");

        var y0Accessor = function(d, i) { return dataseries[i].y; }

        var renderAreaD1 = new Plottable.Plot.Area(dataseries, xScale, yScale);
        var renderAreaD2 = new Plottable.Plot.Area(dataseries_top, xScale, yScale, "x", "y", y0Accessor);

        var fillAccessor = function() { return "steelblue"; }
        var fillAccessorTop = function() { return "pink"; }
        renderAreaD1.project("fill", fillAccessor)
        renderAreaD2.project("fill", fillAccessorTop)

        var gridlines = new Plottable.Component.Gridlines(xScale, yScale);
        var renderGroup = new Plottable.Component.Group([gridlines, renderAreaD1, renderAreaD2]);

        var chart = new Plottable.Template.StandardChart()
                        .center(renderGroup).xAxis(xAxis).yAxis(yAxis)
                        .renderTo("#x-test");

        window.x = new Plottable.Interaction.XDragBox(renderGroup).setupZoomCallback(xScale, null).registerWithComponent();

}

quicktests.push(quicktest)
<commit_msg>Update dragzoom_x.js<commit_after>var quicktests = (quicktests || []);

var quicktest = function(svg, data, Plottable){

        var dataseries = data[0].slice(0, 21);
        var dataseries_top = data[1].slice(0, 21);
        for (var i = 0; i < 20; ++i) {
          dataseries_top[i].x = dataseries[i].x;
          dataseries_top[i].y += dataseries[i].y;
        }

        var xScale = new Plottable.Scale.Linear();
        var xAxis = new Plottable.Axis.XAxis(xScale, "bottom");

        var yScale = new Plottable.Scale.Linear();
        var yAxis = new Plottable.Axis.YAxis(yScale, "left");

        var y0Accessor = function(d, i) { return dataseries[i].y; }

        var renderAreaD1 = new Plottable.Plot.Area(dataseries, xScale, yScale);
        var renderAreaD2 = new Plottable.Plot.Area(dataseries_top, xScale, yScale, "x", "y", y0Accessor);

        var fillAccessor = function() { return "steelblue"; }
        var fillAccessorTop = function() { return "pink"; }
        renderAreaD1.project("fill", fillAccessor)
        renderAreaD2.project("fill", fillAccessorTop)

        var gridlines = new Plottable.Component.Gridlines(xScale, yScale);
        var renderGroup = new Plottable.Component.Group([gridlines, renderAreaD1, renderAreaD2]);

        var chart = new Plottable.Template.StandardChart()
                        .center(renderGroup).xAxis(xAxis).yAxis(yAxis)
                        .renderTo(svg);

        window.x = new Plottable.Interaction.XDragBox(renderGroup).setupZoomCallback(xScale, null).registerWithComponent();

}

quicktests.push(quicktest)
<|endoftext|>