<commit_before>'use strict';

var path = require('path');
var gulp = require('gulp');
var uglify = require("gulp-uglify");
var concat = require('gulp-concat');
var browserify = require('gulp-browserify');
var highlight = require('highlight.js');
var marked = require('marked');
var markdown = require('gulp-markdown');
var refresh = require('gulp-livereload');
var sass = require('gulp-sass');
var express = require("express");
var embedlr = require("gulp-embedlr");
var lr = require('tiny-lr');

var server = lr();
marked.setOptions({
    highlight: function (code) {
        return highlight.highlightAuto(code).value;
    }
});

gulp.task('sass', function () {
    gulp.src('./scss/*.scss')
        .pipe(sass({includePaths: ['scss/includes']}))
        .pipe(gulp.dest('./css'))
        .pipe(refresh(server));
});

gulp.task('markdown', function () {
    gulp.src('./steps/*.md')
        .pipe(markdown())
        .pipe(gulp.dest('./steps'))
});

gulp.task('scripts', function () {
    gulp.src('./js/main.js')
        .pipe(browserify({
            shim: {
                'jquery': {
                    path: './bower_components/jquery/jquery.min.js',
                    exports: '$'
                },
                'handlebars': {
                    path: './bower_components/handlebars/handlebars.js',
                    exports: 'Handlebars'
                },
                'impress': {
                    path: './bower_components/impress.js/js/impress.js',
                    exports: 'impress'
                }
            }
        }))
        .pipe(concat('presentation-bundle.js'))
        .pipe(uglify())
        .pipe(gulp.dest('./js'));
});

gulp.task('livereload', function () {
    server.listen(35729, function (err) {
        if (err) return console.log(err);
    });
    gulp.src("**/*.html")
        .pipe(embedlr())
        .pipe(gulp.dest("./"));
});

gulp.task("connect", function () {
    var app = express();
    app.use(express.query())
        .use(express.bodyParser())
        .use(express.static(path.resolve('./')))
        .use(express.directory(path.resolve('./')))
        .use(lr.middleware({ app: app }))
        .listen(9000);
});

gulp.task('watch', function () {
    gulp.watch('./scss/**/*.scss', function () {
        gulp.run('sass');
    });
    gulp.watch('./steps/*.md', function () {
        gulp.run('markdown');
    });
    gulp.watch('/js/**/*.js', function () {
        gulp.run('scripts');
    })
});

gulp.task('default', function () {
    gulp.run('livereload', 'connect', 'sass', 'scripts', 'watch');
});

<commit_msg>Minor update to step generation gulp task name.<commit_after>'use strict';

var path = require('path');
var gulp = require('gulp');
var uglify = require("gulp-uglify");
var concat = require('gulp-concat');
var browserify = require('gulp-browserify');
var highlight = require('highlight.js');
var marked = require('marked');
var markdown = require('gulp-markdown');
var refresh = require('gulp-livereload');
var sass = require('gulp-sass');
var express = require("express");
var embedlr = require("gulp-embedlr");
var lr = require('tiny-lr');

var server = lr();
marked.setOptions({
    highlight: function (code) {
        return highlight.highlightAuto(code).value;
    }
});

gulp.task('sass', function () {
    gulp.src('./scss/*.scss')
        .pipe(sass({includePaths: ['scss/includes']}))
        .pipe(gulp.dest('./css'))
        .pipe(refresh(server));
});

gulp.task('generate-steps', function () {
    gulp.src('./steps/*.md')
        .pipe(markdown())
        .pipe(gulp.dest('./steps'))
});

gulp.task('scripts', function () {
    gulp.src('./js/main.js')
        .pipe(browserify({
            shim: {
                'jquery': {
                    path: './bower_components/jquery/jquery.min.js',
                    exports: '$'
                },
                'handlebars': {
                    path: './bower_components/handlebars/handlebars.js',
                    exports: 'Handlebars'
                },
                'impress': {
                    path: './bower_components/impress.js/js/impress.js',
                    exports: 'impress'
                }
            }
        }))
        .pipe(concat('presentation-bundle.js'))
        .pipe(uglify())
        .pipe(gulp.dest('./js'));
});

gulp.task('livereload', function () {
    server.listen(35729, function (err) {
        if (err) return console.log(err);
    });
    gulp.src("**/*.html")
        .pipe(embedlr())
        .pipe(gulp.dest("./"));
});

gulp.task("connect", function () {
    var app = express();
    app.use(express.query())
        .use(express.bodyParser())
        .use(express.static(path.resolve('./')))
        .use(express.directory(path.resolve('./')))
        .use(lr.middleware({ app: app }))
        .listen(9000);
});

gulp.task('watch', function () {
    gulp.watch('./scss/**/*.scss', function () {
        gulp.run('sass');
    });
    gulp.watch('./steps/*.md', function () {
        gulp.run('generate-steps');
    });
    gulp.watch('/js/**/*.js', function () {
        gulp.run('scripts');
    })
});

gulp.task('default', function () {
    gulp.run('livereload', 'connect', 'sass', 'scripts', 'watch');
});

<|endoftext|>


<commit_before>
JsGit.HttpRemote = function(repo, name, repoUrl) {
  JsGit.Remote.apply(this, [repo, name, repoUrl])

  this.fetchRefs = function(callback) {
    var remote = this
    $.get(
      this.makeUri('/info/refs'),
      "",
      function(data) {
        var refs = JsGit.HttpRemote.parseInfoRefs(data)
        _(refs).each(function(ref) {
          remote.addRef(ref.name, ref.sha)
        })
        if (callback != "undefined") {
          callback(refs)
        }
      }
    )
  }

  this.fetchHistory = function(sha, max, callback, haveCommits) {
    var commits = haveCommits || []
    if (max == 0) {
      callback(commits)
    } else {
      if (sha == "undefined") {
        callback(commits)
      } else {
        var remote = this;
        this.fetchObject(sha, function(commit) {
          commits.push(commit)
          remote.repo.addObject(commit)
          remote.fetchHistory(commit.parents[0], max-1, callback, commits)
        })
      }
    }
  }

  this.fetchObject = function(sha, callback) {
    this.fetchObjectData(sha, function(data) {
      var object = JsGit.HttpRemote.parseObjectData(sha, data)
      console.log(object)
      callback(object)
    })
  }

  this.fetchObjectData = function(sha, callback) {
    var uri = this.makeObjectUri(sha)
    $.ajax({
      url: uri,
      type: "GET",
      beforeSend: function(xhr) {
        xhr.overrideMimeType('text/plain; charset=x-user-defined')
      },
      success: function(data) {
        callback(data)
      }
    })
  }

  this.makeObjectUri = function(sha) {
    return this.makeUri("/objects/" + sha.slice(0, 2) + "/" + sha.slice(2))
  }
}

// Parses the contents of the .git/info/refs file
JsGit.HttpRemote.parseInfoRefs = function(data) {
  var lines = data.split("\n")
  var refs = []
  _(lines).each(function(line) {
    if (line !== "") {
      var tabStops = line.split("\t")
      var ref = {name: tabStops[1], sha: tabStops[0]}
      refs.push(ref)
    }
  })
  return refs
}

JsGit.HttpRemote.parseObjectData = function(sha, compressedData) {
  var deflated = JsGit.stripZlibHeader(JsGit.stringToBytes(compressedData))
  var data = new BinaryFile(RawDeflate.inflate(JsGit.bytesToString(deflated)).toString())
  var offset = 0

  var peek = function(length) {
    return data.slice(offset, offset + length)
  }

  var rest = function() {
    return data.slice(offset)
  }

  var advance = function(length) {
    offset += length
  }

  var type = JsGit.bytesToString(peek(3))
  advance(3)
  if (type === "com") {
    type = "commit"
    advance(4)
  } else if (type === "blo") {
    type = "blob"
    advance(2)
  } else if (type === "tre") {
    type = "tree"
    advance(2)
  }

  var nextByte = -1
  while (nextByte !== 0) {
    nextByte = peek(1)[0]
    advance(1)
  }
  return JsGit.objects.make(sha, type, JsGit.bytesToString(rest()))
}

<commit_msg>Removed unused fetchHistory function<commit_after>
JsGit.HttpRemote = function(repo, name, repoUrl) {
  JsGit.Remote.apply(this, [repo, name, repoUrl])

  this.fetchRefs = function(callback) {
    var remote = this
    $.get(
      this.makeUri('/info/refs'),
      "",
      function(data) {
        var refs = JsGit.HttpRemote.parseInfoRefs(data)
        _(refs).each(function(ref) {
          remote.addRef(ref.name, ref.sha)
        })
        if (callback != "undefined") {
          callback(refs)
        }
      }
    )
  }

  this.fetchObject = function(sha, callback) {
    this.fetchObjectData(sha, function(data) {
      var object = JsGit.HttpRemote.parseObjectData(sha, data)
      console.log(object)
      callback(object)
    })
  }

  this.fetchObjectData = function(sha, callback) {
    var uri = this.makeObjectUri(sha)
    $.ajax({
      url: uri,
      type: "GET",
      beforeSend: function(xhr) {
        xhr.overrideMimeType('text/plain; charset=x-user-defined')
      },
      success: function(data) {
        callback(data)
      }
    })
  }

  this.makeObjectUri = function(sha) {
    return this.makeUri("/objects/" + sha.slice(0, 2) + "/" + sha.slice(2))
  }
}

// Parses the contents of the .git/info/refs file
JsGit.HttpRemote.parseInfoRefs = function(data) {
  var lines = data.split("\n")
  var refs = []
  _(lines).each(function(line) {
    if (line !== "") {
      var tabStops = line.split("\t")
      var ref = {name: tabStops[1], sha: tabStops[0]}
      refs.push(ref)
    }
  })
  return refs
}

JsGit.HttpRemote.parseObjectData = function(sha, compressedData) {
  var deflated = JsGit.stripZlibHeader(JsGit.stringToBytes(compressedData))
  var data = new BinaryFile(RawDeflate.inflate(JsGit.bytesToString(deflated)).toString())
  var offset = 0

  var peek = function(length) {
    return data.slice(offset, offset + length)
  }

  var rest = function() {
    return data.slice(offset)
  }

  var advance = function(length) {
    offset += length
  }

  var type = JsGit.bytesToString(peek(3))
  advance(3)
  if (type === "com") {
    type = "commit"
    advance(4)
  } else if (type === "blo") {
    type = "blob"
    advance(2)
  } else if (type === "tre") {
    type = "tree"
    advance(2)
  }

  var nextByte = -1
  while (nextByte !== 0) {
    nextByte = peek(1)[0]
    advance(1)
  }
  return JsGit.objects.make(sha, type, JsGit.bytesToString(rest()))
}

<|endoftext|>


<commit_before>/**
 * An implementation of the Linked Data Signatures specification for JSON-LD.
 * This library works in the browser and node.js.
 *
 * @author Dave Longley <dlongley@digitalbazaar.com>
 * @author David I. Lehn <dlehn@digitalbazaar.com>
 * @author Manu Sporny <msporny@digitalbazaar.com>
 *
 * BSD 3-Clause License
 * Copyright (c) 2014-2018 Digital Bazaar, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of the Digital Bazaar, Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
(function(global) {

'use strict';

const Injector = require('./Injector');
const util = require('./util');

// TODO: only require dynamically as needed or according to build
const suites = {
  EcdsaKoblitzSignature2016: require('./suites/EcdsaKoblitzSignature2016'),
  Ed25519Signature2018: require('./suites/Ed25519Signature2018'),
  LinkedDataSignature: require('./suites/LinkedDataSignature'),
  LinkedDataSignature2015: require('./suites/LinkedDataSignature2015'),
  GraphSignature2012: require('./suites/GraphSignature2012'),
  RsaSignature2018: require('./suites/RsaSignature2018')
};

// load locally embedded contexts
const contexts = require('./contexts');

// determine if using node.js or browser
const _nodejs = (
  typeof process !== 'undefined' && process.versions && process.versions.node);
const _browser = !_nodejs &&
  (typeof window !== 'undefined' || typeof self !== 'undefined');

/**
 * Attaches the JSON-LD Signatures API to the given object.
 *
 * @param api the object to attach the signatures API to.
 */
function wrap(api) {

const injector = new Injector();

/* API Constants */
const constants = require('./constants');
Object.assign(api, constants);
api.SUPPORTED_ALGORITHMS = [
  'EcdsaKoblitzSignature2016',
  'Ed25519Signature2018',
  'GraphSignature2012',
  'LinkedDataSignature2015',
  'RsaSignature2018'
];

/* Core API */
api.suites = suites;

/**
 * Signs a JSON-LD document using a digital signature.
 *
 * @param input the JSON-LD document to be signed.
 * @param [options] options to use:
 *          algorithm the algorithm to use, eg: 'Ed25519Signature2018',
 *            'RsaSignature2018'.
 *          [privateKeyPem] A PEM-encoded private key.
 *          [privateKeyBase58] A base85-encoded (Bitcoin/IPFS alphabet)
 *            private key.
 *          [creator] the URL to the paired public key.
 *          [date] an optional date to override the signature date with.
 *          [domain] an optional domain to include in the signature.
 *          [nonce] an optional nonce to include in the signature.
 *          [expansionMap] a custom expansion map that is passed
 *            to the JSON-LD processor; by default a function that will
 *            throw an error when unmapped properties are detected in the
 *            input, use `false` to turn this off and allow unmapped
 *            properties to be dropped or use a custom function.
 *          [proof] a JSON-LD document with options to use for the `proof`
 *            node (e.g. `proofPurpose` or any other custom fields can be
 *            provided here using a context different from security-v2).
 *          [documentLoader(url, [callback(err, remoteDoc)])] the document
 *            loader.
 * @param callback(err, signedDocument) called once the operation completes.
 *
 * @return a Promise that resolves to the signed document.
 */
api.sign = util.callbackify(async function(input, options) {
  options = options || {};

  // no default algorithm; it must be specified
  if(!('algorithm' in options)) {
    throw new TypeError('"options.algorithm" must be specified.');
  }

  const algorithm = options.algorithm;
  if(api.SUPPORTED_ALGORITHMS.indexOf(algorithm) === -1) {
    throw new Error(
      'Unsupported algorithm "' + algorithm + '"; ' +
      '"options.algorithm" must be one of: ' +
      JSON.stringify(api.SUPPORTED_ALGORITHMS));
  }

  options = _addEmbeddedContextDocumentLoader(options);

  // TODO: won't work with static analysis?
  // use signature suite
  //const Suite = require('./suites/' + algorithm);
  const Suite = suites[algorithm];
  return new Suite(injector).sign(input, options);
});

/**
 * Verifies a JSON-LD digitally-signed object.
 *
 * @param obj the JSON-LD object to verify.
 * @param [options] the options to use:
 *          [publicKey] a JSON-LD document providing the public
 *            key info or a function ((keyId, options, [(err, publicKey)]) that
 *            returns a Promise that resolves to such a document (or that
 *            accepts a node-style callback that will be passed it).
 *          [publicKeyOwner] a JSON-LD document providing the public key owner
 *            info including the list of valid keys for that owner or a
 *            function (owner, options, [(err, ownerDoc)]) that returns a
 *            Promise that resolves to such a document (or that accepts a
 *            node-style callback that will be passed it).
 *          [checkNonce(nonce, options, function(err, valid))] a callback to
 *            check if the nonce (null if none) used in the signature is valid.
 *          [checkDomain(domain, options, function(err, valid))] a callback
 *            to check if the domain used (null if none) is valid.
 *          [checkKey(key, options, function(err, trusted))] a callback to
 *            check if the key used to sign the message is trusted.
 *          [checkKeyOwner(owner, key, options, function(err, trusted))] a
 *            callback to check if the key's owner is trusted.
 *          [checkTimestamp]: check signature timestamp (default: false).
 *          [maxTimestampDelta]: signature must be created within a window of
 *            this many seconds (default: 15 minutes).
 *          [documentLoader(url, [callback(err, remoteDoc)])] the document
 *            loader.
 *          [id] the ID (full URL) of the node to check the signature of, if
 *            the input contains multiple signed nodes.
 * @param [callback(err, result)] called once the operation completes.
 *
 * @return a Promise that resolves to the verification result.
 */
api.verify = util.callbackify(async function(input, options) {
  // set default options
  options = Object.assign({}, options || {});

  // validate options
  if('checkNonce' in options &&
    !(options.checkNonce === false ||
    typeof options.checkNonce === 'function')) {
    throw new TypeError(
      '"options.checkNonce" must be `false` or a function.');
  }
  if('checkDomain' in options &&
    !(options.checkDomain === false ||
    typeof options.checkDomain === 'string' ||
    typeof options.checkDomain === 'function')) {
    throw new TypeError(
      '"options.checkDomain" must be `false`, a string, or a function.');
  }
  if('checkTimestamp' in options &&
    !(options.checkTimestamp === false ||
    typeof options.checkTimestamp === 'function')) {
    throw new TypeError(
      '"options.checkTimestamp" must be `false` or a function.');
  }

  // backwards compatibility, massage `getPublicKey` and `getPublicKeyOwner`
  // options into `publicKey` and `publicKeyOwner`
  if('getPublicKey' in options) {
    options.publicKey = options.getPublicKey;
  }
  if('getPublicKeyOwner' in options) {
    options.publicKeyOwner = options.getPublicKeyOwner;
  }

  options = _addEmbeddedContextDocumentLoader(options);

  // TODO: frame before getting signature, not just compact? considerations:
  // 1. named-graph framing support is required to avoid merging data and
  //    invalidating the signature
  // 2. JSON-only inputs will fail compaction -- so perhaps this library
  //    should require the signature to be at the top?
  /*
  const frame = {
    '@context': constants.SECURITY_CONTEXT_URL,
    proof: {},
    signature: {
      type: algorithm,
      created: {},
      creator: {},
      signatureValue: {}
    }
  };
  if(options.id) {
    frame.id = options.id;
  }
  */
  // compact to get signature types
  const jsonld = injector.use('jsonld');
  const opts = {};
  if(options.documentLoader) {
    opts.documentLoader = options.documentLoader;
  }
  const framed = await jsonld.compact(
    input, constants.SECURITY_CONTEXT_URL, opts);

  // ensure there is at least one `proof` or `signature`
  const proofs = jsonld.getValues(framed, 'signature')
    .map(doc => ({property: 'signature', doc}))
    .concat(jsonld.getValues(framed, 'proof')
      .map(doc => ({property: 'proof', doc})));
  if(proofs.length === 0) {
    throw new Error('No signature found.');
  }

  // TODO: this only works for set signatures; add support for chained
  // signatures

  // create a promise for each signature to be verified
  const results = await Promise.all(proofs.map(proof => (async () => {
    try {
      const algorithm = jsonld.getValues(proof.doc, 'type')[0] || '';
      if(api.SUPPORTED_ALGORITHMS.indexOf(algorithm) === -1) {
        throw new Error(
          'Unsupported signature algorithm "' + algorithm +
          '"; ' + 'supported algorithms are: ' +
          JSON.stringify(api.SUPPORTED_ALGORITHMS));
      }

      // copy the framed object and place a single signature on each copy
      const f = util.deepClone(framed);
      f[proof.property] = proof.doc;
      // TODO: won't work with static analysis?
      // use signature suite
      //const Suite = require('./suites/' + algorithm);
      const Suite = suites[algorithm];
      const verified = await new Suite(injector).verify(
        f, Object.assign({}, options, {framed}));
      return {verified};
    } catch(e) {
      return {verified: false, error: e};
    }
  })()));

  // ensure results include public key identifiers
  results.forEach((result, i) => {
    if(proofs[i].doc.creator) {
      result.publicKey = proofs[i].doc.creator;
    }
  });

  return {
    keyResults: results,
    verified: results.every(r => r.verified)
  };
});

function _addEmbeddedContextDocumentLoader(options) {
  options = Object.assign({}, options);
  if(!options.documentLoader) {
    const jsonld = injector.use('jsonld');
    const documentLoader = jsonld.documentLoader;
    options.documentLoader = async url => {
      if(url in contexts) {
        return {
          contextUrl: null,
          documentUrl: url,
          document: contexts[url]
        };
      }
      return documentLoader(url);
    };
  }
  return options;
}

/* Helper functions */
const Helper = require('./Helper');
const helper = new Helper(injector);

// expose for helper functions
api.getPublicKey = util.callbackify(helper.getPublicKey.bind(helper));
api.checkKey = util.callbackify(helper.checkKey.bind(helper));
api.getJsonLd = util.callbackify(helper.getJsonLd.bind(helper));

// expose injector API
api.use = injector.use.bind(injector);

// reexpose API as `.promises` for backwards compatability
api.promises = api;

// expose base64 functions for testing
api._encodeBase64Url = util.encodeBase64Url;
api._decodeBase64Url = util.decodeBase64Url;

return api;

} // end wrap

// used to generate a new verifier API instance
const factory = function() {
  return wrap(function() {return factory();});
};
wrap(factory);

if(_nodejs) {
  // export nodejs API
  module.exports = factory;
} else if(typeof define === 'function' && define.amd) {
  // export AMD API
  define([], function() {
    return factory;
  });
} else if(_browser) {
  // export simple browser API
  if(typeof global.jsigs === 'undefined') {
    global.jsigs = {};
  }
  wrap(global.jsigs);
}

})(typeof window !== 'undefined' ? window : this);
<commit_msg>Compute supported algorithms at runtime to allow for extensions.<commit_after>/**
 * An implementation of the Linked Data Signatures specification for JSON-LD.
 * This library works in the browser and node.js.
 *
 * @author Dave Longley <dlongley@digitalbazaar.com>
 * @author David I. Lehn <dlehn@digitalbazaar.com>
 * @author Manu Sporny <msporny@digitalbazaar.com>
 *
 * BSD 3-Clause License
 * Copyright (c) 2014-2018 Digital Bazaar, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of the Digital Bazaar, Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
(function(global) {

'use strict';

const Injector = require('./Injector');
const util = require('./util');

// TODO: only require dynamically as needed or according to build
const suites = {
  EcdsaKoblitzSignature2016: require('./suites/EcdsaKoblitzSignature2016'),
  Ed25519Signature2018: require('./suites/Ed25519Signature2018'),
  LinkedDataSignature: require('./suites/LinkedDataSignature'),
  LinkedDataSignature2015: require('./suites/LinkedDataSignature2015'),
  GraphSignature2012: require('./suites/GraphSignature2012'),
  RsaSignature2018: require('./suites/RsaSignature2018')
};

// load locally embedded contexts
const contexts = require('./contexts');

// determine if using node.js or browser
const _nodejs = (
  typeof process !== 'undefined' && process.versions && process.versions.node);
const _browser = !_nodejs &&
  (typeof window !== 'undefined' || typeof self !== 'undefined');

/**
 * Attaches the JSON-LD Signatures API to the given object.
 *
 * @param api the object to attach the signatures API to.
 */
function wrap(api) {

const injector = new Injector();

/* API Constants */
const constants = require('./constants');
Object.assign(api, constants);

/* Core API */
api.suites = suites;

/**
 * Signs a JSON-LD document using a digital signature.
 *
 * @param input the JSON-LD document to be signed.
 * @param [options] options to use:
 *          algorithm the algorithm to use, eg: 'Ed25519Signature2018',
 *            'RsaSignature2018'.
 *          [privateKeyPem] A PEM-encoded private key.
 *          [privateKeyBase58] A base85-encoded (Bitcoin/IPFS alphabet)
 *            private key.
 *          [creator] the URL to the paired public key.
 *          [date] an optional date to override the signature date with.
 *          [domain] an optional domain to include in the signature.
 *          [nonce] an optional nonce to include in the signature.
 *          [expansionMap] a custom expansion map that is passed
 *            to the JSON-LD processor; by default a function that will
 *            throw an error when unmapped properties are detected in the
 *            input, use `false` to turn this off and allow unmapped
 *            properties to be dropped or use a custom function.
 *          [proof] a JSON-LD document with options to use for the `proof`
 *            node (e.g. `proofPurpose` or any other custom fields can be
 *            provided here using a context different from security-v2).
 *          [documentLoader(url, [callback(err, remoteDoc)])] the document
 *            loader.
 * @param callback(err, signedDocument) called once the operation completes.
 *
 * @return a Promise that resolves to the signed document.
 */
api.sign = util.callbackify(async function(input, options) {
  options = options || {};

  // no default algorithm; it must be specified
  if(!('algorithm' in options)) {
    throw new TypeError('"options.algorithm" must be specified.');
  }

  const SUPPORTED_ALGORITHMS = _getSupportedAlgorithms();

  const algorithm = options.algorithm;
  if(SUPPORTED_ALGORITHMS.indexOf(algorithm) === -1) {
    throw new Error(
      'Unsupported algorithm "' + algorithm + '"; ' +
      '"options.algorithm" must be one of: ' +
      JSON.stringify(SUPPORTED_ALGORITHMS));
  }

  options = _addEmbeddedContextDocumentLoader(options);

  // TODO: won't work with static analysis?
  // use signature suite
  //const Suite = require('./suites/' + algorithm);
  const Suite = suites[algorithm];
  return new Suite(injector).sign(input, options);
});

/**
 * Verifies a JSON-LD digitally-signed object.
 *
 * @param obj the JSON-LD object to verify.
 * @param [options] the options to use:
 *          [publicKey] a JSON-LD document providing the public
 *            key info or a function ((keyId, options, [(err, publicKey)]) that
 *            returns a Promise that resolves to such a document (or that
 *            accepts a node-style callback that will be passed it).
 *          [publicKeyOwner] a JSON-LD document providing the public key owner
 *            info including the list of valid keys for that owner or a
 *            function (owner, options, [(err, ownerDoc)]) that returns a
 *            Promise that resolves to such a document (or that accepts a
 *            node-style callback that will be passed it).
 *          [checkNonce(nonce, options, function(err, valid))] a callback to
 *            check if the nonce (null if none) used in the signature is valid.
 *          [checkDomain(domain, options, function(err, valid))] a callback
 *            to check if the domain used (null if none) is valid.
 *          [checkKey(key, options, function(err, trusted))] a callback to
 *            check if the key used to sign the message is trusted.
 *          [checkKeyOwner(owner, key, options, function(err, trusted))] a
 *            callback to check if the key's owner is trusted.
 *          [checkTimestamp]: check signature timestamp (default: false).
 *          [maxTimestampDelta]: signature must be created within a window of
 *            this many seconds (default: 15 minutes).
 *          [documentLoader(url, [callback(err, remoteDoc)])] the document
 *            loader.
 *          [id] the ID (full URL) of the node to check the signature of, if
 *            the input contains multiple signed nodes.
 * @param [callback(err, result)] called once the operation completes.
 *
 * @return a Promise that resolves to the verification result.
 */
api.verify = util.callbackify(async function(input, options) {
  // set default options
  options = Object.assign({}, options || {});

  // validate options
  if('checkNonce' in options &&
    !(options.checkNonce === false ||
    typeof options.checkNonce === 'function')) {
    throw new TypeError(
      '"options.checkNonce" must be `false` or a function.');
  }
  if('checkDomain' in options &&
    !(options.checkDomain === false ||
    typeof options.checkDomain === 'string' ||
    typeof options.checkDomain === 'function')) {
    throw new TypeError(
      '"options.checkDomain" must be `false`, a string, or a function.');
  }
  if('checkTimestamp' in options &&
    !(options.checkTimestamp === false ||
    typeof options.checkTimestamp === 'function')) {
    throw new TypeError(
      '"options.checkTimestamp" must be `false` or a function.');
  }

  // backwards compatibility, massage `getPublicKey` and `getPublicKeyOwner`
  // options into `publicKey` and `publicKeyOwner`
  if('getPublicKey' in options) {
    options.publicKey = options.getPublicKey;
  }
  if('getPublicKeyOwner' in options) {
    options.publicKeyOwner = options.getPublicKeyOwner;
  }

  options = _addEmbeddedContextDocumentLoader(options);

  // TODO: frame before getting signature, not just compact? considerations:
  // 1. named-graph framing support is required to avoid merging data and
  //    invalidating the signature
  // 2. JSON-only inputs will fail compaction -- so perhaps this library
  //    should require the signature to be at the top?
  /*
  const frame = {
    '@context': constants.SECURITY_CONTEXT_URL,
    proof: {},
    signature: {
      type: algorithm,
      created: {},
      creator: {},
      signatureValue: {}
    }
  };
  if(options.id) {
    frame.id = options.id;
  }
  */
  // compact to get signature types
  const jsonld = injector.use('jsonld');
  const opts = {};
  if(options.documentLoader) {
    opts.documentLoader = options.documentLoader;
  }
  const framed = await jsonld.compact(
    input, constants.SECURITY_CONTEXT_URL, opts);

  // ensure there is at least one `proof` or `signature`
  const proofs = jsonld.getValues(framed, 'signature')
    .map(doc => ({property: 'signature', doc}))
    .concat(jsonld.getValues(framed, 'proof')
      .map(doc => ({property: 'proof', doc})));
  if(proofs.length === 0) {
    throw new Error('No signature found.');
  }

  // TODO: this only works for set signatures; add support for chained
  // signatures

  // create a promise for each signature to be verified
  const SUPPORTED_ALGORITHMS = _getSupportedAlgorithms();
  const results = await Promise.all(proofs.map(proof => (async () => {
    try {
      const algorithm = jsonld.getValues(proof.doc, 'type')[0] || '';
      if(SUPPORTED_ALGORITHMS.indexOf(algorithm) === -1) {
        throw new Error(
          'Unsupported signature algorithm "' + algorithm +
          '"; ' + 'supported algorithms are: ' +
          JSON.stringify(SUPPORTED_ALGORITHMS));
      }

      // copy the framed object and place a single signature on each copy
      const f = util.deepClone(framed);
      f[proof.property] = proof.doc;
      // TODO: won't work with static analysis?
      // use signature suite
      //const Suite = require('./suites/' + algorithm);
      const Suite = suites[algorithm];
      const verified = await new Suite(injector).verify(
        f, Object.assign({}, options, {framed}));
      return {verified};
    } catch(e) {
      return {verified: false, error: e};
    }
  })()));

  // ensure results include public key identifiers
  results.forEach((result, i) => {
    if(proofs[i].doc.creator) {
      result.publicKey = proofs[i].doc.creator;
    }
  });

  return {
    keyResults: results,
    verified: results.every(r => r.verified)
  };
});

function _getSupportedAlgorithms() {
  // every suite is supported except the base class
  return Object.keys(api.suites).filter(s => s !== 'LinkedDataSignature');
}

function _addEmbeddedContextDocumentLoader(options) {
  options = Object.assign({}, options);
  if(!options.documentLoader) {
    const jsonld = injector.use('jsonld');
    const documentLoader = jsonld.documentLoader;
    options.documentLoader = async url => {
      if(url in contexts) {
        return {
          contextUrl: null,
          documentUrl: url,
          document: contexts[url]
        };
      }
      return documentLoader(url);
    };
  }
  return options;
}

/* Helper functions */
const Helper = require('./Helper');
const helper = new Helper(injector);

// expose for helper functions
api.getPublicKey = util.callbackify(helper.getPublicKey.bind(helper));
api.checkKey = util.callbackify(helper.checkKey.bind(helper));
api.getJsonLd = util.callbackify(helper.getJsonLd.bind(helper));

// expose injector API
api.use = injector.use.bind(injector);

// reexpose API as `.promises` for backwards compatability
api.promises = api;

// expose base64 functions for testing
api._encodeBase64Url = util.encodeBase64Url;
api._decodeBase64Url = util.decodeBase64Url;

return api;

} // end wrap

// used to generate a new verifier API instance
const factory = function() {
  return wrap(function() {return factory();});
};
wrap(factory);

if(_nodejs) {
  // export nodejs API
  module.exports = factory;
} else if(typeof define === 'function' && define.amd) {
  // export AMD API
  define([], function() {
    return factory;
  });
} else if(_browser) {
  // export simple browser API
  if(typeof global.jsigs === 'undefined') {
    global.jsigs = {};
  }
  wrap(global.jsigs);
}

})(typeof window !== 'undefined' ? window : this);
<|endoftext|>


<commit_before>'use strict';

import angular from 'angular';
import 'angular-ui-router';
import 'angular-animate';
import 'angular-aria';
// import 'angular-messages.min.js';
import 'angular-material';

import 'angular-material/angular-material.min.css';

if (ON_TEST) {
  require('angular-mocks');
}

const ngModule = angular
  .module('app', [
    'ui.router',
    'ngMaterial'
  ])
  /* This config block requires manual ng-annotation with an array or @ngInject.
    The build pipeline requires this, else it errors out. */
  .config( // @ngInject
   $locationProvider => {
    // Enable HTML5 Location Mode
    $locationProvider.hashPrefix('!');
  });

import commonDirectives from './common/directives';
commonDirectives(ngModule);

import commonServices from './common/services';
commonServices(ngModule);

import home from './home';
home(ngModule);

/**
 * Add new feature folders here
 * Example: About Us
 */
// import about from './about';
// about(ngModule);
<commit_msg>Linting updates<commit_after>'use strict';

import angular from 'angular';
import 'angular-ui-router';
import 'angular-animate';
import 'angular-aria';
// import 'angular-messages.min.js';
import 'angular-material';

import 'angular-material/angular-material.min.css';

if (ON_TEST) {
  require('angular-mocks');
}

const ngModule = angular
  .module('app', [
    'ui.router',
    'ngMaterial'
  ])
/* This config block requires manual ng-annotation with an array or @ngInject.
  The build pipeline requires this, else it errors out. */
  /* eslint-disable */
  .config( // @ngInject
  /* eslint-enable */
    $locationProvider => {
      // Enable HTML5 Location Mode
      $locationProvider.hashPrefix('!');
    });

import commonDirectives from './common/directives';
commonDirectives(ngModule);

import commonServices from './common/services';
commonServices(ngModule);

import home from './home';
home(ngModule);

/**
 * Add new feature folders here
 * Example: About Us
 */
// import about from './about';
// about(ngModule);
<|endoftext|>


<commit_before>console.log("");
console.log("-- Started [<%= name %>] --");
console.log("\n\n");
<commit_msg>Output details from package.json for sample "Start" condition.<commit_after>import packageJson from "../package.json";
console.log(`\n-- Started: ${ packageJson.name }@${ packageJson.version } --\n\n`);
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict'

const URL = require('url')
const TrackingProtection = require('tracking-protection').CTPParser
const DataFile = require('./dataFile')
const Filtering = require('./filtering')
const LRUCache = require('lru_cache/core').LRUCache

module.exports.resourceName = 'trackingProtection'

let trackingProtection

let cachedFirstParty = new LRUCache(50)

// Temporary whitelist until we find a better solution
const whitelistHosts = ['connect.facebook.net', 'connect.facebook.com', 'staticxx.facebook.com', 'www.facebook.com', 'scontent.xx.fbcdn.net', 'pbs.twimg.com', 'scontent-sjc2-1.xx.fbcdn.net', 'platform.twitter.com', 'syndication.twitter.com']

const startTrackingProtection = (wnd) => {
  Filtering.registerBeforeRequestFilteringCB((details) => {
    const firstPartyUrl = URL.parse(details.firstPartyUrl)
    let firstPartyUrlHost = firstPartyUrl.hostname || ''
    if (firstPartyUrlHost.startsWith('www.')) {
      firstPartyUrlHost = firstPartyUrlHost.substring(4)
    }
    if (firstPartyUrl.protocol && firstPartyUrl.protocol.startsWith('http')) {
      if (!cachedFirstParty.get(firstPartyUrlHost)) {
        let firstPartyHosts = trackingProtection.findFirstPartyHosts(firstPartyUrlHost)
        cachedFirstParty.put(firstPartyUrlHost, firstPartyHosts && firstPartyHosts.split(',') || [])
      }
    }
    const urlHost = URL.parse(details.url).hostname
    const cancel = firstPartyUrl.protocol &&
      details.resourceType !== 'mainFrame' &&
      firstPartyUrl.protocol.startsWith('http') &&
      !whitelistHosts.includes(urlHost) &&
      cachedFirstParty.get(firstPartyUrlHost) &&
      trackingProtection.matchesTracker(firstPartyUrlHost, urlHost) &&
      urlHost !== firstPartyUrl.hostname &&
      !cachedFirstParty.get(firstPartyUrlHost).find((baseHost) =>
        !Filtering.isThirdPartyHost(baseHost, urlHost))

    DataFile.debug(module.exports.resourceName, details, cancel)
    return {
      cancel,
      resourceName: module.exports.resourceName
    }
  })
}

module.exports.init = () => {
  trackingProtection = new TrackingProtection()
  DataFile.init(module.exports.resourceName, startTrackingProtection,
                (data) => trackingProtection.deserialize(data))
}
<commit_msg>Unblock embedding of twitter timelines<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict'

const URL = require('url')
const TrackingProtection = require('tracking-protection').CTPParser
const DataFile = require('./dataFile')
const Filtering = require('./filtering')
const LRUCache = require('lru_cache/core').LRUCache

module.exports.resourceName = 'trackingProtection'

let trackingProtection

let cachedFirstParty = new LRUCache(50)

// Temporary whitelist until we find a better solution
const whitelistHosts = ['connect.facebook.net', 'connect.facebook.com', 'staticxx.facebook.com', 'www.facebook.com', 'scontent.xx.fbcdn.net', 'pbs.twimg.com', 'scontent-sjc2-1.xx.fbcdn.net', 'platform.twitter.com', 'syndication.twitter.com', 'cdn.syndication.twimg.com']

const startTrackingProtection = (wnd) => {
  Filtering.registerBeforeRequestFilteringCB((details) => {
    const firstPartyUrl = URL.parse(details.firstPartyUrl)
    let firstPartyUrlHost = firstPartyUrl.hostname || ''
    if (firstPartyUrlHost.startsWith('www.')) {
      firstPartyUrlHost = firstPartyUrlHost.substring(4)
    }
    if (firstPartyUrl.protocol && firstPartyUrl.protocol.startsWith('http')) {
      if (!cachedFirstParty.get(firstPartyUrlHost)) {
        let firstPartyHosts = trackingProtection.findFirstPartyHosts(firstPartyUrlHost)
        cachedFirstParty.put(firstPartyUrlHost, firstPartyHosts && firstPartyHosts.split(',') || [])
      }
    }
    const urlHost = URL.parse(details.url).hostname
    const cancel = firstPartyUrl.protocol &&
      details.resourceType !== 'mainFrame' &&
      firstPartyUrl.protocol.startsWith('http') &&
      !whitelistHosts.includes(urlHost) &&
      cachedFirstParty.get(firstPartyUrlHost) &&
      trackingProtection.matchesTracker(firstPartyUrlHost, urlHost) &&
      urlHost !== firstPartyUrl.hostname &&
      !cachedFirstParty.get(firstPartyUrlHost).find((baseHost) =>
        !Filtering.isThirdPartyHost(baseHost, urlHost))

    DataFile.debug(module.exports.resourceName, details, cancel)
    return {
      cancel,
      resourceName: module.exports.resourceName
    }
  })
}

module.exports.init = () => {
  trackingProtection = new TrackingProtection()
  DataFile.init(module.exports.resourceName, startTrackingProtection,
                (data) => trackingProtection.deserialize(data))
}
<|endoftext|>


<commit_before>import { NEW_SECTION } from '../actions/sectionActions';
import { NEW_ENTRY } from '../actions/entryActions';
import { NEW_FIELD } from '../actions/fieldActions';
import store from './store';

export default class SocketEvents {
  constructor(socket, dispatch) {
    this.dispatch = dispatch;
    this.socket = socket;
  }

  newField() {
    const { fields } = store.getState().fields;
    this.socket.on('new-field', (newField) => {
      if (!fields.some(field => field._id === newField._id)) {
        this.dispatch({ type: NEW_FIELD, json: newField });
      }
    });
  }

  newEntry() {
    const { entries } = store.getState().entries;
    console.log(store.getState());
    this.socket.on('new-entry', (newEntry) => {
      if (!entries.some(entry => entry._id === newEntry._id)) {
        this.dispatch({ type: NEW_ENTRY, json: newEntry });
      }
    });
  }

  deleteEntry() {
    this.socket.on('delete-entry', deletedId => console.log(deletedId));
  }

  newSection() {
    const { sections } = store.getState().sections;
    this.socket.on('new-section', (newSection) => {
      if (!sections.some(section => section._id === newSection._id)) {
        this.dispatch({ type: NEW_SECTION, newSection });
      }
    });
  }

  deleteSection() {
    this.socket.on('delete-section', deletedId => console.log(deletedId));
  }

  listen() {
    this.newField();

    this.newEntry();
    this.deleteEntry();

    this.newSection();
    this.deleteSection();
  }
}
<commit_msg>:wrench: Fix socket events<commit_after>import { NEW_SECTION } from '../actions/sectionActions';
import { NEW_ENTRY } from '../actions/entryActions';
import { NEW_FIELD } from '../actions/fieldActions';
import store from './store';

export default class SocketEvents {
  constructor(socket, dispatch) {
    this.dispatch = dispatch;
    this.socket = socket;
  }

  newField() {
    this.socket.on('new-field', (newField) => {
      const { fields } = store.getState().fields;
      if (!fields.some(field => field._id === newField._id)) {
        this.dispatch({ type: NEW_FIELD, json: newField });
      }
    });
  }

  deleteField() {
    this.socket.on('delete-field', deletedId => console.log(deletedId));
  }

  newEntry() {
    this.socket.on('new-entry', (newEntry) => {
      const { entries } = store.getState().entries;
      if (!entries.some(entry => entry._id === newEntry._id)) {
        this.dispatch({ type: NEW_ENTRY, json: newEntry });
      }
    });
  }

  deleteEntry() {
    this.socket.on('delete-entry', deletedId => console.log(deletedId));
  }

  newSection() {
    this.socket.on('new-section', (newSection) => {
      const { sections } = store.getState().sections;
      if (!sections.some(section => section._id === newSection._id)) {
        this.dispatch({ type: NEW_SECTION, newSection });
      }
    });
  }

  deleteSection() {
    this.socket.on('delete-section', deletedId => console.log(deletedId));
  }

  listen() {
    // Listen for new or delete Fields
    this.newField();
    this.deletedField();

    // Listen for new or delete Entries
    this.newEntry();
    this.deleteEntry();

    // Listen for new or delete Sections
    this.newSection();
    this.deleteSection();
  }
}
<|endoftext|>


<commit_before>const html = require('choo/html')
const css = require('csjs-inject')
const C = require('../lib/constants')
const fuzzy = require('fuzzy')
const sortBy = require('lodash/sortBy')

const style = css`

.autocomplete {
  position: absolute;
  top: 100%;
  left: 40px;
  width: 300px;
  max-height: 60vw;
  overflow-y: scroll;
  flex-direction: column;
  background: ${C.DARKBLUE};
  z-index: 1001;
}

.tagrow {
  position: relative;
  height: 40px;
  padding: 10px;
  justify-content: space-between;
  flex-direction: row;
  font-family: CooperHewitt-Light;
  font-size: 1.2em;
  color: ${C.LIGHTGREY};
}

.match {
  font-weight: bold;
  color: ${C.YELLOW};
}

.more {
  color: ${C.LIGHTGREY};
  font-style: italic;
  opacity: 0.5;
}

`

const matchopts = {
  pre: `<span class=${style.match}>`,
  post: ''
}

module.exports = (state, prev, send) => {
  const tagquery = state.currentsearch.tagquery

  if (!((typeof tagquery) === 'string')) return html``

  function tagcount (tag) {
    return {
      string: tag.string,
      original: tag.original,
      count: state.tags.tags[tag.original].length
    }
  }

  function taghits () {
    const alltags = Object.keys(state.tags.tags).filter(t => t !== '__local')
    if (tagquery.length === 0) {
      return alltags.map((tag) => {
        return {
          string: tag,
          original: tag
        }
      })
    } else {
      return fuzzy.filter(tagquery, alltags, matchopts)
    }
  }

  function row (tag) {
    const row = html`

      ${html(`<div>${tag.string}</div>`)}

${tag.count}

    `

    row.onclick = (e) => {
      e.preventDefault()
      send('search_addtag', { tag: tag.original })
    }

    return row
  }

  function rows () {
    const hits = taghits().map(tagcount)

    const sorted = sortBy(hits, (tag) => {
      return 0 - tag.count
    })

    const fullrows = sorted.map((hit) => {
      return row(hit)
    }).slice(0, C.AUTOMAXTAGS)

    if (sorted.length > C.AUTOMAXTAGS) {
      const diff = sorted.length - C.AUTOMAXTAGS
      const more = html`

... and ${diff} more tags

      `
      fullrows.push(more)
    }

    return fullrows
  }

  return html`

    ${rows()}

  `
}
<commit_msg>Fixup of autocomplete fixup<commit_after>const html = require('choo/html')
const css = require('csjs-inject')
const C = require('../lib/constants')
const fuzzy = require('fuzzy')
const sortBy = require('lodash/sortBy')

const style = css`

.autocomplete {
  position: absolute;
  top: 100%;
  left: 40px;
  width: 300px;
  max-height: 60vh;
  overflow-y: scroll;
  flex-direction: column;
  background: ${C.DARKBLUE};
  z-index: 1001;
}

.tagrow {
  position: relative;
  height: 40px;
  padding: 10px;
  justify-content: space-between;
  flex-direction: row;
  font-family: CooperHewitt-Light;
  font-size: 1.2em;
  color: ${C.LIGHTGREY};
}

.match {
  font-weight: bold;
  color: ${C.YELLOW};
}

.more {
  color: ${C.LIGHTGREY};
  font-style: italic;
  opacity: 0.5;
}

`

const matchopts = {
  pre: `<span class=${style.match}>`,
  post: ''
}

module.exports = (state, prev, send) => {
  const tagquery = state.currentsearch.tagquery

  if (!((typeof tagquery) === 'string')) return html``

  function tagcount (tag) {
    return {
      string: tag.string,
      original: tag.original,
      count: state.tags.tags[tag.original].length
    }
  }

  function taghits () {
    const alltags = Object.keys(state.tags.tags).filter(t => t !== '__local')
    if (tagquery.length === 0) {
      return alltags.map((tag) => {
        return {
          string: tag,
          original: tag
        }
      })
    } else {
      return fuzzy.filter(tagquery, alltags, matchopts)
    }
  }

  function row (tag) {
    const row = html`

      ${html(`<div>#${tag.string}</div>`)}

${tag.count}

    `

    row.onclick = (e) => {
      e.preventDefault()
      send('search_addtag', { tag: tag.original })
    }

    return row
  }

  function rows () {
    const hits = taghits().map(tagcount)

    const sorted = sortBy(hits, (tag) => {
      return 0 - tag.count
    })

    const fullrows = sorted.map((hit) => {
      return row(hit)
    })

    return fullrows
  }

  return html`

    ${rows()}

  `
}
<|endoftext|>


<commit_before>/*jslint browser: true, sloppy: true, todo: true, devel: true */
/*global $, it, describe, xit, xdescribe, expect, beforeEach, loadFixtures, spyOn, InteractiveModule, ScrollTrack */

//= require waypoints
//= require waypoints-sticky
//= require scroll-interactive-mod

describe('ScrollTrack', function () {
    var scrollTrack;

    beforeEach(function () {
        loadFixtures('interactive-scrolling.html');
        scrollTrack = new ScrollTrack($('.content-mod'), $('#end-scroll-track'));
    });

    it('has attributes', function () {
        expect(scrollTrack.contentModule).toBeDefined();
        expect(scrollTrack.trackEnd).toBeDefined();
        expect(scrollTrack.contentModule).toBe('div.content-mod');
        expect(scrollTrack.trackEnd).toBe('div#end-scroll-track');
    });

    describe('getTop', function () {
        it('returns the offset of the content module', function () {
            expect(scrollTrack.getTop()).toBe($('.content-mod').offset().top);
        });
    });

    describe('getBottom', function () {
        it('returns the offset of the end-scroll-track div', function () {
            expect(scrollTrack.getBottom()).toBe($('#end-scroll-track').offset().top);
        });
    });

    describe('getHeight', function () {
        it('returns the difference between its top and its bottom', function () {
            expect(scrollTrack.getHeight()).toBe($('#end-scroll-track').offset().top - $('.content-mod').offset().top);
        });
    });

    describe('isScrollable', function () {
        describe('when the track is taller than the interactive', function () {
            it('returns true', function () {
                expect(scrollTrack.isScrollable(200)).toBe(true);
            });
        });

        describe('when the track is shorter than the interactive', function () {
            it('returns false', function () {
                expect(scrollTrack.isScrollable(800)).toBe(false);
            });
        });

        describe('when the track is undefined', function () {
            it('returns false', function () {
                scrollTrack.trackEnd = $('#noSuchDiv');
                expect(scrollTrack.isScrollable(200)).toBe(false);
            });
        });
    });
});

describe('InteractiveModule', function () {
    var interactive, scrollTrack;

    beforeEach(function () {
        loadFixtures('interactive-scrolling.html');
        scrollTrack = new ScrollTrack($('.content-mod'), $('#end-scroll-track'));
        interactive = new InteractiveModule($('.pinned'), scrollTrack);
    });

    it('has attributes', function () {
        expect(interactive.bMargin).toBeDefined();
        expect(interactive.topBuffer).toBeDefined();
        expect(interactive.fudgeFactor).toBeDefined();
        expect(interactive.module).toBe('div.pinned');
    });

    describe('fixTop()', function () {
        describe('when the interactive is taller than its column', function () {
            beforeEach(function () {
                spyOn(ScrollTrack.prototype, 'isScrollable').andReturn(false);
                // Have to re-create the test objects to incorporate the spy
                scrollTrack = new ScrollTrack($('.content-mod'), $('#end-scroll-track'));
                interactive = new InteractiveModule($('.pinned'), scrollTrack);
                interactive.module.removeClass('stuck');
            });

            it('does nothing', function () {
                interactive.fixTop();
                expect(scrollTrack.isScrollable).toHaveBeenCalled();
                expect(interactive.module).not.toHaveClass('stuck');
            });
        });

        describe('when the interactive is shorter than its column', function () {
            beforeEach(function () {
                interactive.fixTop();
            });

            it('adds the "stuck" class', function () {
                expect(interactive.module).toHaveClass('stuck');
            });

            xit('adds a CSS width', function () {
                expect(interactive.module.css('width')).toBe(interactive.iWidth + 'px');
            });
        });
    });

    describe('unFixTop()', function () {
        beforeEach(function () {
            interactive.module.addClass('stuck');
            interactive.unFixTop();
        });

        it('removes the "stuck" class', function () {
            expect(interactive.module).not.toHaveClass('stuck');
        });
    });

    describe('fixBottom()', function () {
        beforeEach(function () {
            interactive.module.addClass('bottomed');
            interactive.fixBottom();
        });

        it('adds the "stuck" class', function () {
            expect(interactive.module).toHaveClass('stuck');
        });

        it('removes the "bottomed" class', function () {
            expect(interactive.module).not.toHaveClass('bottomed');
        });
    });

    describe('unFixBottom()', function () {
        beforeEach(function () {
            interactive.module.addClass('stuck');
            interactive.unFixBottom();
        });

        it('removes the "stuck" class', function () {
            expect(interactive.module).not.toHaveClass('stuck');
        });

        it('adds the "bottomed" class', function () {
            expect(interactive.module).toHaveClass('bottomed');
        });
    });

    describe('waypoints control', function () {
        beforeEach(function () {
            interactive.setWaypoints();
        });

        describe('setWaypoints', function () {
            it('sets two waypoints', function () {
                expect($.waypoints().vertical.length).toBe(2);
            });
        });

        describe('clearWaypoints', function () {
            it('removes established waypoints', function () {
                expect($.waypoints().vertical.length).toBe(4);
                interactive.clearWaypoints();
                expect($.waypoints().vertical.length).toBe(0);
            });
        });

        describe('disableWaypoints', function () {
            xit('disables existing waypoints', function () {
                expect($.waypoints().vertical.length).toBe(2);
                interactive.disableWaypoints();
                expect($.waypoints().vertical[0].enabled).toBe(false);
            });
        });

        describe('enableWaypoints', function () {
            xit('enables disabled waypoints', function () {
                expect($.waypoints().vertical.length).toBe(4);
                interactive.enableWaypoints();
                expect($.waypoints().vertical[0].enabled).toBe(true);
            });
        });
    });

    describe('getHeight()', function () {
        it('returns the value of the i_height attribute', function () {
            expect(interactive.getHeight()).toBe(interactive.iHeight);
        });
    });
});
<commit_msg>A little better setup for waypoints testing<commit_after>/*jslint browser: true, sloppy: true, todo: true, devel: true */
/*global $, it, describe, xit, xdescribe, expect, beforeEach, loadFixtures, spyOn, InteractiveModule, ScrollTrack */

//= require waypoints
//= require waypoints-sticky
//= require scroll-interactive-mod

describe('ScrollTrack', function () {
    var scrollTrack;

    beforeEach(function () {
        loadFixtures('interactive-scrolling.html');
        scrollTrack = new ScrollTrack($('.content-mod'), $('#end-scroll-track'));
    });

    it('has attributes', function () {
        expect(scrollTrack.contentModule).toBeDefined();
        expect(scrollTrack.trackEnd).toBeDefined();
        expect(scrollTrack.contentModule).toBe('div.content-mod');
        expect(scrollTrack.trackEnd).toBe('div#end-scroll-track');
    });

    describe('getTop', function () {
        it('returns the offset of the content module', function () {
            expect(scrollTrack.getTop()).toBe($('.content-mod').offset().top);
        });
    });

    describe('getBottom', function () {
        it('returns the offset of the end-scroll-track div', function () {
            expect(scrollTrack.getBottom()).toBe($('#end-scroll-track').offset().top);
        });
    });

    describe('getHeight', function () {
        it('returns the difference between its top and its bottom', function () {
            expect(scrollTrack.getHeight()).toBe($('#end-scroll-track').offset().top - $('.content-mod').offset().top);
        });
    });

    describe('isScrollable', function () {
        describe('when the track is taller than the interactive', function () {
            it('returns true', function () {
                expect(scrollTrack.isScrollable(200)).toBe(true);
            });
        });

        describe('when the track is shorter than the interactive', function () {
            it('returns false', function () {
                expect(scrollTrack.isScrollable(800)).toBe(false);
            });
        });

        describe('when the track is undefined', function () {
            it('returns false', function () {
                scrollTrack.trackEnd = $('#noSuchDiv');
                expect(scrollTrack.isScrollable(200)).toBe(false);
            });
        });
    });
});

describe('InteractiveModule', function () {
    var interactive, scrollTrack;

    beforeEach(function () {
        loadFixtures('interactive-scrolling.html');
        scrollTrack = new ScrollTrack($('.content-mod'), $('#end-scroll-track'));
        interactive = new InteractiveModule($('.pinned'), scrollTrack);
    });

    it('has attributes', function () {
        expect(interactive.bMargin).toBeDefined();
        expect(interactive.topBuffer).toBeDefined();
        expect(interactive.fudgeFactor).toBeDefined();
        expect(interactive.module).toBe('div.pinned');
    });

    describe('fixTop()', function () {
        describe('when the interactive is taller than its column', function () {
            beforeEach(function () {
                spyOn(ScrollTrack.prototype, 'isScrollable').andReturn(false);
                // Have to re-create the test objects to incorporate the spy
                scrollTrack = new ScrollTrack($('.content-mod'), $('#end-scroll-track'));
                interactive = new InteractiveModule($('.pinned'), scrollTrack);
                interactive.module.removeClass('stuck');
            });

            it('does nothing', function () {
                interactive.fixTop();
                expect(scrollTrack.isScrollable).toHaveBeenCalled();
                expect(interactive.module).not.toHaveClass('stuck');
            });
        });

        describe('when the interactive is shorter than its column', function () {
            beforeEach(function () {
                interactive.fixTop();
            });

            it('adds the "stuck" class', function () {
                expect(interactive.module).toHaveClass('stuck');
            });

            xit('adds a CSS width', function () {
                expect(interactive.module.css('width')).toBe(interactive.iWidth + 'px');
            });
        });
    });

    describe('unFixTop()', function () {
        beforeEach(function () {
            interactive.module.addClass('stuck');
            interactive.unFixTop();
        });

        it('removes the "stuck" class', function () {
            expect(interactive.module).not.toHaveClass('stuck');
        });
    });

    describe('fixBottom()', function () {
        beforeEach(function () {
            interactive.module.addClass('bottomed');
            interactive.fixBottom();
        });

        it('adds the "stuck" class', function () {
            expect(interactive.module).toHaveClass('stuck');
        });

        it('removes the "bottomed" class', function () {
            expect(interactive.module).not.toHaveClass('bottomed');
        });
    });

    describe('unFixBottom()', function () {
        beforeEach(function () {
            interactive.module.addClass('stuck');
            interactive.unFixBottom();
        });

        it('removes the "stuck" class', function () {
            expect(interactive.module).not.toHaveClass('stuck');
        });

        it('adds the "bottomed" class', function () {
            expect(interactive.module).toHaveClass('bottomed');
        });
    });

    describe('waypoints control', function () {
        beforeEach(function () {
            $.waypoints('destroy'); // Clear existing waypoints
            interactive.setWaypoints();
        });

        describe('setWaypoints', function () {
            it('sets two waypoints', function () {
                expect($.waypoints().vertical.length).toBe(2);
            });
        });

        describe('clearWaypoints', function () {
            it('removes established waypoints', function () {
                expect($.waypoints().vertical.length).toBe(2);
                interactive.clearWaypoints();
                expect($.waypoints().vertical.length).toBe(0);
            });
        });

        describe('disableWaypoints', function () {
            xit('disables existing waypoints', function () {
                expect($.waypoints().vertical.length).toBe(2);
                interactive.disableWaypoints();
                expect($.waypoints().vertical[0].enabled).toBe(false);
            });
        });

        describe('enableWaypoints', function () {
            xit('enables disabled waypoints', function () {
                expect($.waypoints().vertical.length).toBe(4);
                interactive.enableWaypoints();
                expect($.waypoints().vertical[0].enabled).toBe(true);
            });
        });
    });

    describe('getHeight()', function () {
        it('returns the value of the i_height attribute', function () {
            expect(interactive.getHeight()).toBe(interactive.iHeight);
        });
    });
});
<|endoftext|>


<commit_before>// Scoping
var pokedex = {
    // Returns n as an integer iff it is valid and min <= n <= max; otherwise,
    // returns undefined.
    'parse_integer': function(n, min, max) {
        var parsed = parseInt(n);
        if (isNaN(parsed) || parsed != n || n < min || n > max)
            return undefined;

        return parsed;
    },

    // Javascript versions of pokedex.formulae.  Unfortunate to duplicate this,
    // but it's pretty simple and the alternatives are ajax or source code
    // translation.
    'formulae': {
        'calculated_stat': function(params) {
            // Need to fake floor division
            return Math.floor(
                (params.base_stat * 2
                    + params.iv
                    + Math.floor(params.effort / 4))
                * params.level / 100) + 5;
        },
        'calculated_hp': function(params) {
            // Shedinja
            if (params.base_stat == 1)
                return 1;

            return Math.floor(
                (params.base_stat * 2
                    + params.iv
                    + Math.floor(params.effort / 4))
                * params.level / 100) + 10 + params.level;
        },

        'earned_exp': function(params) {
            return Math.floor(params.base_exp * params.level / 7);
        },
    },
};

// Stuff for collapsing Pokémon-move tables
pokedex.pokemon_moves = {
    'init': function() {
        $('.dex-pokemon-moves').each(function() {
            var $this = $(this);
            // Remember the arrangement of version columns and separators
            // on page load
            var column_classes = [];
            var num_generations = 0;
            var $version_columns = $this.find('col.dex-col-version');
            $version_columns.each(function() {
                column_classes.push(this.className);
                if ($(this).hasClass('dex-col-last-version')) {
                    num_generations++;
                }
            });
            $this.data('pokemon_moves.first_generation',
                       pokedex.generation_ct - num_generations + 1);
            $this.data('pokemon_moves.column_classes', column_classes);

            // Remember the arrangement of rows, including header rows.  Using
            // references to the rows themselves will also let us delete them
            // at will and restore them later, which makes resetting to page
            // default much easier
            var row_order = $this.find('tr').get();
            $this.data('pokemon_moves.original_rows', row_order);

            // Bind action to the available links
            // Add a row with controls
            var $controls = $('

');
            var vg_start = 0;
            var vg_width = 0;
            for (var i = 0; i < $version_columns.length; i++) {
                vg_width++;
                if ($( $version_columns[i] )
                    .hasClass('dex-col-last-version'))
                {
                    var $control = $(
                        ''
                        + ''
                        + '	'
                    );

                    $control.data(
                        'pokemon_moves.column_endpoints', {
                            'start': vg_start,
                            'end':   vg_start + vg_width - 1
                        }
                    );
                    $control.click(pokedex.pokemon_moves.filter_columns);
                    $controls.append($control);
                    vg_start += vg_width;
                    vg_width = 0;
                }
            }
            var $first_tr = $( $this.find('tr')[0] );
            $first_tr.before($controls);

            // Initially filter to only the most recent games, i.e., the last
            // column
            $controls.find('td:last-child').click();
        });
    },

    // Reset filtering, sorting, etc.
    'restore': function(e) {
        var $td = $(e.target).closest('td');
        var $this = $td.closest('table.dex-pokemon-moves');

        // Restore rows -- BEFORE unhiding everything, dummy.
        // note: Don't delete the row with filter links in it!
        $this.find('tr:not(.js-dex-pokemon-moves-controls)').remove();
        $( $this.data('pokemon_moves.original_rows') ).appendTo($this);

        // Ok, now unhide everything
        $this.find('td, th').css('display', null);

        // Restore column definitions
        $this.find('col.dex-col-version').remove();
        var column_classes = $this.data('pokemon_moves.column_classes');
        for (var col = column_classes.length; col >= 1; col--) {
            $this.prepend('<col class="' + column_classes[col - 1] + '">');
        }

        // Change the filter link back
        var $img = $td.find('img');
        $td.unbind('click', pokedex.pokemon_moves.restore);
        $td.click(pokedex.pokemon_moves.filter_columns);
        $img.attr({
            'src':   '/static/spline/icons/funnel.png',
            'alt':   'Filter',
            'title': 'Filter'
        });
    },

    // Show only the columns numbered in the passed list
    'filter_columns': function(e) {
        var $td = $(e.target).closest('td');
        var $tr = $(e.target).closest('tr');
        var $this = $tr.closest('table.dex-pokemon-moves');
        // First unhide everything
        $this.find('td, th').css('display', null);

        // Get the indexes of the columns in the selected generation
        var columns_hash = {};
        var start_end = $td.data('pokemon_moves.column_endpoints');
        for (var i = start_end.start; i <= start_end.end; i++) {
            columns_hash[i + 1] = 1;
        }

        // Write out only the <col> tags for the columns we're keeping
        var column_classes = $this.data('pokemon_moves.column_classes');
        var hidden_cell_css = [];
        var visible_cell_css = [];
        $this.find('col.dex-col-version').remove();
        for (var col = column_classes.length; col >= 1; col--) {
            if (columns_hash[col]) {
                $this.prepend('<col class="' + column_classes[col - 1] + '">');
                visible_cell_css.push(':nth-child(' + col + ')')
            }
            else {
                hidden_cell_css.push(':nth-child(' + col + ')')
            }
        }

        // Hide the appropriate cells in every row that isn't mucked up by
        // colspan (i.e. a subheader or the control row)
        var $non_spanned_rows = $this.find('tr:not(.subheader-row)')
                                     .not($tr);
        var $cells = $non_spanned_rows.find('th, td');
        $cells.filter( hidden_cell_css.join(',') )
              .css('display', 'none');
        $this.find('col.dex-col-version')
             .filter( hidden_cell_css.join(',') )
             .css('display', 'none');

        // Hide any rows that only have empty cells remaining
        // i.e. hide any rows that AREN'T rows WITHOUT empty cells remaining.
        var $visible_cells = $cells.filter( visible_cell_css.join(',') )
        var $relevant_rows = $visible_cells.not('.tutored, :empty')
                                           .closest('tr');
        // Tutors have a different notion of "empty", as each cell is its own
        // mini table thing.  They're empty iff they have no img children.
        var $relevant_tutor_rows = $visible_cells.filter('.tutored')
                                                 .children('img')
                                                 .closest('tr');
        $relevant_rows = $relevant_rows.add($relevant_tutor_rows);
        $non_spanned_rows.not($relevant_rows).remove();

        // Similarly filter the control row
        $tr.find('td').not($td).css('display', 'none');

        // Set the lone remaining filter icon to unfilter
        var $img = $td.find('img');
        $td.unbind('click', pokedex.pokemon_moves.filter_columns);
        $td.click(pokedex.pokemon_moves.restore);
        $img.attr({
            'src':   '/static/spline/icons/overlay/funnel--minus.png',
            'alt':   'Unfilter',
            'title': 'Unfilter'
        });
    },
};

$(function() { pokedex.pokemon_moves.init() });

// Easter egg: obdurate
$(function() {
    $('.dex-obdurate').each(function() {
        var $this = $(this);
        var text = $this.text();

        // Wrap words in these so the browser doesn't break in the middle of a
        // "word"
        var nobr_start = '<nobr>';
        var nobr_end = '</nobr><wbr>';

        var newtext = [ nobr_start ];

        for (var i = 0; i < text.length; i++) {
            var ch = text.substr(i, 1);
            newtext.push('');

            if (ch == ' ') {
                // Allow break on spaces
                newtext.push(nobr_end);
                newtext.push(nobr_start);
            }
        }

        newtext.push(nobr_end);
        $this.html(newtext.join(''));
    });
});
<commit_msg>Added sorting to pokemon-moves tables.  #54<commit_after>// Scoping
var pokedex = {
    // Returns n as an integer iff it is valid and min <= n <= max; otherwise,
    // returns undefined.
    'parse_integer': function(n, min, max) {
        var parsed = parseInt(n);
        if (isNaN(parsed) || parsed != n || n < min || n > max)
            return undefined;

        return parsed;
    },

    // Javascript versions of pokedex.formulae.  Unfortunate to duplicate this,
    // but it's pretty simple and the alternatives are ajax or source code
    // translation.
    'formulae': {
        'calculated_stat': function(params) {
            // Need to fake floor division
            return Math.floor(
                (params.base_stat * 2
                    + params.iv
                    + Math.floor(params.effort / 4))
                * params.level / 100) + 5;
        },
        'calculated_hp': function(params) {
            // Shedinja
            if (params.base_stat == 1)
                return 1;

            return Math.floor(
                (params.base_stat * 2
                    + params.iv
                    + Math.floor(params.effort / 4))
                * params.level / 100) + 10 + params.level;
        },

        'earned_exp': function(params) {
            return Math.floor(params.base_exp * params.level / 7);
        },
    },
};

// Stuff for collapsing Pokémon-move tables
pokedex.pokemon_moves = {
    'init': function() {
        $('.dex-pokemon-moves').each(function() {
            var $this = $(this);
            var $first_tr = $this.find('tr').eq(0);

            // Remember the arrangement of version columns and separators
            // on page load
            var column_classes = [];
            var num_generations = 0;
            var $version_columns = $this.find('col.dex-col-version');
            $version_columns.each(function() {
                column_classes.push(this.className);
                if ($(this).hasClass('dex-col-last-version')) {
                    num_generations++;
                }
            });
            $this.data('pokemon_moves.first_generation',
                       pokedex.generation_ct - num_generations + 1);
            $this.data('pokemon_moves.column_classes', column_classes);
            $this.data('pokemon_moves.version_column_count',
                       $version_columns.length);

            // Remember the arrangement of rows, including header rows.  Using
            // references to the rows themselves will also let us delete them
            // at will and restore them later, which makes resetting to page
            // default much easier
            var row_order = $this.find('tr').get();
            $this.data('pokemon_moves.original_rows', row_order);

            var $controls = $('

');

            // Create buttons for filtering by generation
            var vg_start = 0;
            var vg_width = 0;
            var $last_filter_control;
            for (var i = 0; i < $version_columns.length; i++) {
                vg_width++;
                if ($( $version_columns[i] )
                    .hasClass('dex-col-last-version'))
                {
                    var $control = $(
                        ''
                        + ''
                        + '	'
                    );

                    $control.data(
                        'pokemon_moves.column_endpoints', {
                            'start': vg_start,
                            'end':   vg_start + vg_width - 1
                        }
                    );
                    $last_filter_control = $control;
                    $control.click(pokedex.pokemon_moves.filter_columns);
                    $controls.append($control);
                    vg_start += vg_width;
                    vg_width = 0;
                }
            }

            // Create buttons for sorting by a column
            var $first_tds = $first_tr.find('td, th');
            var num_columns = $first_tds.length;
            for (var i = $version_columns.length; i < num_columns; i++) {
                var is_numeric = false;
                var column_title = $first_tds.eq(i).text();
                // Fair warning: this is brittle
                if (column_title == 'PP'  || column_title == 'Power' ||
                    column_title == 'Acc' || column_title == 'Pri')
                {
                    is_numeric = true;
                }

                var $control = $(
                    ''
                    + ''
                    + '	'
                );
                $control.data('pokemon_moves.column', i + 1);
                $control.data('pokemon_moves.is_numeric', is_numeric);
                $control.click(pokedex.pokemon_moves.sort_rows);
                $controls.append($control);
            }

            // Add controls to the document
            $first_tr.before($controls);

            // Initially filter to only the most recent games, i.e., the last
            // column
            $last_filter_control.click();
        });
    },

    //// These functions handle restoring the table to its original state.
    //// There isn't a simple way to undo a sort without affecting the
    //// generation filter or vice versa.  Also, when a filter is removed, the
    //// table needs to be reset (in case the filter removed any rows) and then
    //// resorted.  So if either is disabled, we actually just reset the entire
    //// table and then re-apply whatever's appropriate.

    // Reset filtering
    'unfilter': function(e) {
        var $td = $(e.target).closest('td');
        var $table = $td.closest('table.dex-pokemon-moves');
        var $controls = $table.find('tr.js-dex-pokemon-moves-controls');

        // Restore column definitions
        $table.find('col.dex-col-version').remove();
        var column_classes = $table.data('pokemon_moves.column_classes');
        for (var col = column_classes.length; col >= 1; col--) {
            $table.prepend('<col class="' + column_classes[col - 1] + '">');
        }

        // Unhide everything in the controls row
        $controls.find('td, th').css('display', null);

        // Unhide generation columns in ALL rows and unmark them as filtered
        var $original_rows = $( $table.data('pokemon_moves.original_rows') );
        $original_rows.find('td, th').css('display', null);
        $original_rows.removeData('pokemon_moves.filtered_out');

        // Blank the table
        $table.find('tr')
               .not('.js-dex-pokemon-moves-controls')
               .slice(1)  // leave the first header row
               .remove();

        // Repopulate the table, to restore any filtered-out rows
        var sorted_rows = $table.data('pokemon_moves.sorted_rows');
        var $sorted_rows;
        if (sorted_rows) $sorted_rows = $(sorted_rows);
        else             $sorted_rows = $original_rows;
        $table.append($sorted_rows);

        // Reset filter links
        $td.unbind('click', pokedex.pokemon_moves.unfilter);
        $td.click(pokedex.pokemon_moves.filter_columns);
        $td.find('img').attr({
            'src':   '/static/spline/icons/funnel.png',
            'alt':   'Filter',
            'title': 'Filter'
        });
    },

    // Reset sorting
    'unsort': function(e) {
        var $td = $(e.target).closest('td');
        var $table = $td.closest('table.dex-pokemon-moves');

        $table.removeData('pokemon_moves.sorted_rows');

        // Restore list of rows...  but only if they haven't been removed by
        // filtering
        // Can't use remove() here, as it clears the data() on the rows
        $table.find('tr:not(.js-dex-pokemon-moves-controls)').each(function() {
            this.parentNode.removeChild(this);
        });
        var $original_rows = $( $table.data('pokemon_moves.original_rows') );
        var $unfiltered_rows = $original_rows.filter(function() {
            return ! $(this).data('pokemon_moves.filtered_out');
        });

        $table.append($unfiltered_rows);

        // Reset sort links
        $td.unbind('click', pokedex.pokemon_moves.reset);
        $td.click(pokedex.pokemon_moves.sort_rows);
        var is_numeric = $td.data('pokemon_moves.is_numeric');
        $td.find('img').attr({
            'src':   '/static/spline/icons/sort-'
                     + (is_numeric ? 'number-descending' : 'alphabet')
                     + '.png',
            'alt':   'Sort',
            'title': 'Sort'
        });
    },

    //// Actually do filtering or sorting

    // Show only the columns numbered in the passed list
    'filter_columns': function(e) {
        var $td = $(e.target).closest('td');
        var $tr = $(e.target).closest('tr');
        var $this = $tr.closest('table.dex-pokemon-moves');
        // First unhide everything
        $this.find('td, th').css('display', null);

        // Get the indexes of the columns in the selected generation
        var columns_hash = {};
        var start_end = $td.data('pokemon_moves.column_endpoints');
        for (var i = start_end.start; i <= start_end.end; i++) {
            columns_hash[i + 1] = 1;
        }

        // Write out only the <col> tags for the columns we're keeping
        var column_classes = $this.data('pokemon_moves.column_classes');
        var hidden_cell_css = [];
        var visible_cell_css = [];
        $this.find('col.dex-col-version').remove();
        for (var col = column_classes.length; col >= 1; col--) {
            if (columns_hash[col]) {
                $this.prepend('<col class="' + column_classes[col - 1] + '">');
                visible_cell_css.push(':nth-child(' + col + ')')
            }
            else {
                hidden_cell_css.push(':nth-child(' + col + ')')
            }
        }

        // Hide the appropriate cells in every row that isn't mucked up by
        // colspan (i.e. a subheader or the control row)
        var $all_rows = $( $this.data('pokemon_moves.original_rows') );
        var $non_spanned_rows = $all_rows.not('.subheader-row')
                                         .not($tr);
        var $cells = $non_spanned_rows.find('th, td');
        $cells.filter( hidden_cell_css.join(',') )
              .css('display', 'none');
        $this.find('col.dex-col-version')
             .filter( hidden_cell_css.join(',') )
             .css('display', 'none');

        // Hide any rows that only have empty cells remaining
        // i.e. hide any rows that AREN'T rows WITHOUT empty cells remaining.
        var $visible_cells = $cells.filter( visible_cell_css.join(',') )
        var $relevant_rows = $visible_cells.not('.tutored, :empty')
                                           .closest('tr');
        // Tutors have a different notion of "empty", as each cell is its own
        // mini table thing.  They're empty iff they have no img children.
        var $relevant_tutor_rows = $visible_cells.filter('.tutored')
                                                 .children('img')
                                                 .closest('tr');
        $relevant_rows = $relevant_rows.add($relevant_tutor_rows);

        var $empty_rows = $non_spanned_rows.not($relevant_rows);
        // Must do remove() FIRST, as it nukes data()
        $empty_rows.remove()
                   .data('pokemon_moves.filtered_out', true);

        // Similarly filter the control row
        $tr.find('td.js-dex-pokemon-moves-filter-link')
           .not($td)
           .css('display', 'none');

        // Set the lone remaining filter icon to unfilter
        var $img = $td.find('img');
        $td.unbind('click', pokedex.pokemon_moves.filter_columns);
        $td.click(pokedex.pokemon_moves.unfilter);
        $img.attr({
            'src':   '/static/spline/icons/overlay/funnel--minus.png',
            'alt':   'Unfilter',
            'title': 'Unfilter'
        });
    },

    // Sort the table by the selected column
    'sort_rows': function(e) {
        var $tr = $(e.target).closest('tr');
        var $this = $tr.closest('table.dex-pokemon-moves');
        var $td = $(e.target).closest('td');

        // Multi-step process, here.
        // 1. Find all the rows that aren't the control row or the first header
        //    row.
        // 2. Save the rows that contain actual data.
        // 3. Delete everything.
        // 4. Sort the saved rows.
        // 5. Reinsert the saved rows.
        var $rows = $this.find('tr').not($tr);
        $rows = $rows.slice(1);
        // Can't use remove() here, as it clears the data() on the rows
        $rows.each(function() {
            this.parentNode.removeChild(this);
        });

        // Get a list of all the data rows -- i.e., not the headers
        var data_rows = $( $this.data('pokemon_moves.original_rows') )
            .not('.header-row, .subheader-row').get();

        // Create a sort function.
        // Experimentation reveals that Array.sort(func) will actually call
        // func() for every pair of items that need comparing—which is O(n²) in
        // the worst case.  Let's cache the sort key for each row, then, and
        // remove it afterwards.
        var column_idx = $td.data('pokemon_moves.column');
        var is_numeric = $td.data('pokemon_moves.is_numeric');
        var name_column_idx = 1 + $this.data('pokemon_moves.version_column_count');
        $(data_rows).each(function() {
            var $this = $(this);
            // Use html() over text() so types sort correctly.  Yes, that means
            // the sort key is '...'.  Gross.
            var sortkey = $this.find('td:nth-child(' + column_idx + ')')
                               .html();
            if (is_numeric) {
                sortkey = parseInt(sortkey);
                // Negative so biggest comes first
                if (sortkey) sortkey *= -1;
                else sortkey = 0;
            }
            $this.data('pokemon_moves.sortkey1', sortkey);

            // Name is always fallback
            var name = $this.find('td:nth-child(' + name_column_idx + ')')
                            .text();
            $this.data('pokemon_moves.sortkey2', name);
        });
        var sort_callback = function(a, b) {
            var a_value = $(a).data('pokemon_moves.sortkey1');
            var b_value = $(b).data('pokemon_moves.sortkey1');
            if      (a_value > b_value) return  1;
            else if (a_value < b_value) return -1;

            // Try names as a fallback
            var a_name = $(a).data('pokemon_moves.sortkey2');
            var b_name = $(b).data('pokemon_moves.sortkey2');
            if      (a_name > b_name) return  1;
            else if (a_name < b_name) return -1;

            // Equal, as far as we care
            return 0;
        };

        // Sort the rows
        data_rows.sort(sort_callback);

        // Some of the rows are hidden by the generation filter and won't be
        // shown, but the order of ALL the rows needs to be saved so the filter
        // can be removed without having to re-sort everything
        $this.data('pokemon_moves.sorted_rows', data_rows);
        var $data_rows = $(data_rows).filter(function() {
            return ! $(this).data('pokemon_moves.filtered_out');
        });
        $this.append($data_rows);

        // Change the link to unsort
        var $img = $td.find('img');
        $td.unbind('click', pokedex.pokemon_moves.sort_rows);
        $td.click(pokedex.pokemon_moves.unsort);
        $img.attr({
            'src':   '/static/spline/icons/overlay/sort--minus.png',
            'alt':   'Unsort',
            'title': 'Unsort'
        });
    },
};

$(function() { pokedex.pokemon_moves.init() });

// Easter egg: obdurate
$(function() {
    $('.dex-obdurate').each(function() {
        var $this = $(this);
        var text = $this.text();

        // Wrap words in these so the browser doesn't break in the middle of a
        // "word"
        var nobr_start = '<nobr>';
        var nobr_end = '</nobr><wbr>';

        var newtext = [ nobr_start ];

        for (var i = 0; i < text.length; i++) {
            var ch = text.substr(i, 1);
            newtext.push('');

            if (ch == ' ') {
                // Allow break on spaces
                newtext.push(nobr_end);
                newtext.push(nobr_start);
            }
        }

        newtext.push(nobr_end);
        $this.html(newtext.join(''));
    });
});
<|endoftext|>


<commit_before>/**
 * @flow
 * All About Olaf
 * Index view
 */

import './globalize-fetch'
import './setup-moment'
import OneSignal from 'react-native-onesignal'

import React from 'react'
import {Provider} from 'react-redux'
import {store} from './flux'
import {tracker} from './analytics'
import {AppNavigator} from './navigation'
import type {NavigationState} from 'react-navigation'

// gets the current screen from navigation state
function getCurrentRouteName(navigationState: NavigationState): ?string {
  if (!navigationState) {
    return null
  }
  const route = navigationState.routes[navigationState.index]
  // dive into nested navigators
  if (route.routes) {
    return getCurrentRouteName(route)
  }
  return route.routeName
}

export default class App extends React.Component {
  componentDidMount() {
    OneSignal.addEventListener('received', this.onReceived)
    OneSignal.addEventListener('opened', this.onOpened)
    OneSignal.addEventListener('registered', this.onRegistered)
    OneSignal.addEventListener('ids', this.onIds)
  }

  componentWillUnmount() {
    OneSignal.removeEventListener('received', this.onReceived)
    OneSignal.removeEventListener('opened', this.onOpened)
    OneSignal.removeEventListener('registered', this.onRegistered)
    OneSignal.removeEventListener('ids', this.onIds)
  }

  onReceived(notification: any) {
    console.log('Notification received:', notification)
  }

  onOpened(openResult: any) {
    console.log('Message:', openResult.notification.payload.body)
    console.log('Data:', openResult.notification.payload.additionalData)
    console.log('isActive:', openResult.notification.isAppInFocus)
    console.log('openResult:', openResult)
  }

  onRegistered(notifData: any) {
    console.log('Device is now registered for push notifications!', notifData)
  }

  onIds(device: any) {
    console.log('Device info:', device)
  }

  trackScreenChanges(
    prevState: NavigationState,
    currentState: NavigationState,
  ) {
    const currentScreen = getCurrentRouteName(currentState)
    const prevScreen = getCurrentRouteName(prevState)

    if (currentScreen !== prevScreen) {
      tracker.trackScreenView(currentScreen)
    }
  }

  render() {
    return (
      <Provider store={store}>
        <AppNavigator onNavigationStateChange={this.trackScreenChanges} />
      </Provider>
    )
  }
}
<commit_msg>disable onesignal in js<commit_after>/**
 * @flow
 * All About Olaf
 * Index view
 */

import './globalize-fetch'
import './setup-moment'
// import OneSignal from 'react-native-onesignal'

import React from 'react'
import {Provider} from 'react-redux'
import {store} from './flux'
import {tracker} from './analytics'
import {AppNavigator} from './navigation'
import type {NavigationState} from 'react-navigation'

// gets the current screen from navigation state
function getCurrentRouteName(navigationState: NavigationState): ?string {
  if (!navigationState) {
    return null
  }
  const route = navigationState.routes[navigationState.index]
  // dive into nested navigators
  if (route.routes) {
    return getCurrentRouteName(route)
  }
  return route.routeName
}

export default class App extends React.Component {
  // componentDidMount() {
  //   OneSignal.addEventListener('received', this.onReceived)
  //   OneSignal.addEventListener('opened', this.onOpened)
  //   OneSignal.addEventListener('registered', this.onRegistered)
  //   OneSignal.addEventListener('ids', this.onIds)
  // }

  // componentWillUnmount() {
  //   OneSignal.removeEventListener('received', this.onReceived)
  //   OneSignal.removeEventListener('opened', this.onOpened)
  //   OneSignal.removeEventListener('registered', this.onRegistered)
  //   OneSignal.removeEventListener('ids', this.onIds)
  // }

  // onReceived(notification: any) {
  //   console.log('Notification received:', notification)
  // }

  // onOpened(openResult: any) {
  //   console.log('Message:', openResult.notification.payload.body)
  //   console.log('Data:', openResult.notification.payload.additionalData)
  //   console.log('isActive:', openResult.notification.isAppInFocus)
  //   console.log('openResult:', openResult)
  // }

  // onRegistered(notifData: any) {
  //   console.log('Device is now registered for push notifications!', notifData)
  // }

  // onIds(device: any) {
  //   console.log('Device info:', device)
  // }

  trackScreenChanges(
    prevState: NavigationState,
    currentState: NavigationState,
  ) {
    const currentScreen = getCurrentRouteName(currentState)
    const prevScreen = getCurrentRouteName(prevState)

    if (currentScreen !== prevScreen) {
      tracker.trackScreenView(currentScreen)
    }
  }

  render() {
    return (
      <Provider store={store}>
        <AppNavigator onNavigationStateChange={this.trackScreenChanges} />
      </Provider>
    )
  }
}
<|endoftext|>


<commit_before>"use strict";
var servicesNav =[
	{
		'id': 'services',
		'label': 'Services',
		'checkPermission':{
			'service':'dashboard',
			'route':'/services/list'
		},
		'url': '#/services',
		'tplPath': 'modules/services/directives/list.tmpl',
		'icon': 'tree',
		'mainMenu': true,
		'tracker': true,
		'scripts': ['modules/services/config.js', 'modules/services/controller.js'],
		'ancestor': ['Home']
	}
];
navigation = navigation.concat(servicesNav);<commit_msg>changed services icon in main menu.<commit_after>"use strict";
var servicesNav =[
	{
		'id': 'services',
		'label': 'Services',
		'checkPermission':{
			'service':'dashboard',
			'route':'/services/list'
		},
		'url': '#/services',
		'tplPath': 'modules/services/directives/list.tmpl',
		'icon': 'cloud',
		'mainMenu': true,
		'tracker': true,
		'scripts': ['modules/services/config.js', 'modules/services/controller.js'],
		'ancestor': ['Home']
	}
];
navigation = navigation.concat(servicesNav);<|endoftext|>