<commit_before>'use strict';
var yeoman = require('yeoman-generator');
var path = require('path');
var chalk = require('chalk');
var yosay = require('yosay');
var _ = require('lodash');
var options = {};

module.exports = yeoman.generators.Base.extend({
  initializing: function () {
    this.pkg = require('../package.json');
    // Have Yeoman greet the user.
    this.log(yosay(
      'Welcome to the fantabulous ' + chalk.red('Phase2') + ' generator! ' + this.pkg.version
    ));
    options = _.assign({
      skipWelcome: true
    }, this.options);
  },

  prompting: function () {
    var done = this.async();
    var prompts = [];

    var gadgetPrompts = require('generator-gadget/lib/prompts.js');
    gadgetPrompts.forEach(function (item) {
      if (_.isUndefined(options[item])) {
        prompts.push(item);
      }
    });

    prompts.push({
      name: 'useENV',
      type: 'confirm',
      message: 'Use Phase2 DevTools/Docker Environment?'
    });
    var envPrompts = require('generator-p2-env/lib/prompts.js');
    envPrompts.forEach(function (item) {
      if (_.isUndefined(options[item])) {
        var validate = item.when;
        item.when = function(answers) {
          return answers['useENV'] && (!_.isFunction(validate) || validate(answers));
        };
        prompts.push(item);
      }
    });

    prompts.push({
      name: 'usePLS',
      type: 'confirm',
      message: 'Use Pattern Lab Starter?'
    });
    var plPrompts = require('generator-pattern-lab-starter/app/prompts.js');
    plPrompts.forEach(function (item) {
      if (_.isUndefined(options[item])) {
        var validate = item.when;
        item.when = function(answers) {
          return answers['usePLS'] && (!_.isFunction(validate) || validate(answers));
        };
        prompts.push(item);
      }
    });

    // Minimally necessary prompt as a fallback if the other generators drop it.
    if (_.isUndefined(options.projectName)) {
      prompts.push({
        name: 'projectName',
        message: 'Machine name of project?',
        default: _.last(this.env.cwd.split('/')), // parent folder
        validate: function (input) {
          return (input.search(' ') === -1) ? true : 'No spaces allowed.';
        }
      });
    }

    // ensuring we only ask questions with the same `name` value once; earlier questions take priority
    prompts = _.uniq(prompts, 'name');

    this.prompt(prompts, function (props) {
      options = _.assign(options, props);
      options.themePath = 'src/themes/' + options.themeName;
      options['skip-readme'] = true;
      done();
    }.bind(this));

  },

  writing: {
    gadget: function () {
      this.composeWith('gadget', {
        options: _.assign(options, {
          themeScripts: {
            "compile-theme": "npm run compile",
            "validate": "npm run test"
          }
        })
      },
      {
        local: require.resolve('generator-gadget')
      });
    },
    env: function() {
      if (options['useENV']) {
        this.composeWith('p2-env', { options: options }, {
          local: require.resolve('generator-p2-env')
        });
      }
    },
    pls: function() {
      if (options['usePLS']) {
        this.composeWith('pattern-lab-starter', { options: options }, {
          local: require.resolve('generator-pattern-lab-starter')
        });
      }
    },

    readme: function() {
      if (options['useENV']) {
        var tokens = require('generator-gadget/lib/util').tokens(options);
        // Yeoman's dependencies cannot handle dynamic partial includes.
        // This is not currently used.
        tokens.gadgetPath = path.resolve(require.resolve('generator-gadget'), 'app/templates/README.md');
        tokens = _.merge(options, tokens);

        this.fs.copyTpl(
          this.templatePath('README.md'),
          this.destinationPath('README.md'),
          tokens
        );
      }
    }
  },

  install: function () {
    this.installDependencies({
      skipInstall: this.options['skip-install']
    });
  }
});
<commit_msg>Added replay feature and stabilized no-PLS results.<commit_after>'use strict';
var yeoman = require('yeoman-generator');
var path = require('path');
var chalk = require('chalk');
var yosay = require('yosay');
var _ = require('lodash');
var options = {};

module.exports = yeoman.generators.Base.extend({
  initializing: function () {
    this.pkg = require('../package.json');
    // Have Yeoman greet the user.
    this.log(yosay(
      'Welcome to the fantabulous ' + chalk.red('Phase2') + ' generator! ' + this.pkg.version
    ));
    options = _.assign({
      skipWelcome: true
    }, this.options);
  },

  prompting: function () {
    var addBehaviors = function() {
      if (options['themeName']) {
        options.themePath = 'src/themes/' + options.themeName;
      }
      options['skip-readme'] = true;
      // If using Docker-based environment defer running install locally.
      if (options['useENV']) {
        options['skip-install'] = true;
      }
    }

    if (options['replay']) {
      options = _.assign(options, this.config.getAll());
      // Ensure the drupalDistro plugin is loaded for this value when sidestepping
      // the prompt filter.
      if (options.hasOwnProperty('drupalDistro') && typeof options.drupalDistro === 'string') {
        var distros = require('generator-gadget/app/distros');
        options.drupalDistro = distros[options.drupalDistro];
      }
      addBehaviors();
    }
    else {
      var done = this.async();
      var prompts = [];

      var gadgetPrompts = require('generator-gadget/lib/prompts.js');
      gadgetPrompts.forEach(function (item) {
        if (_.isUndefined(options[item])) {
          item.default = this.config.get(item.name) || item.default;
          prompts.push(item);
        }
      }.bind(this));

      prompts.push({
        name: 'useENV',
        type: 'confirm',
        message: 'Use Phase2 DevTools/Docker Environment?'
      });
      var envPrompts = require('generator-p2-env/lib/prompts.js');
      envPrompts.forEach(function (item) {
        if (_.isUndefined(options[item])) {
          var validate = item.when;
          item.when = function(answers) {
            return answers['useENV'] && (!_.isFunction(validate) || validate(answers));
          };
          item.default = this.config.get(item.name) || item.default;

          prompts.push(item);
        }
      }.bind(this));

      prompts.push({
        name: 'usePLS',
        type: 'confirm',
        message: 'Use Pattern Lab Starter?'
      });
      var plPrompts = require('generator-pattern-lab-starter/app/prompts.js');
      plPrompts.forEach(function (item) {
        if (_.isUndefined(options[item])) {
          var validate = item.when;
          item.when = function(answers) {
            return answers['usePLS'] && (!_.isFunction(validate) || validate(answers));
          };
          item.default = this.config.get(item.name) || item.default;

          prompts.push(item);
        }
      }.bind(this));

      // Minimally necessary prompt as a fallback if the other generators drop it.
      if (_.isUndefined(options.projectName)) {
        prompts.push({
          name: 'projectName',
          message: 'Machine name of project?',
          default: this.config.get('projectName') || _.last(this.env.cwd.split('/')), // parent folder
          validate: function (input) {
            return (input.search(' ') === -1) ? true : 'No spaces allowed.';
          }
        });
      }

      // ensuring we only ask questions with the same `name` value once; earlier questions take priority
      prompts = _.uniq(prompts, 'name');

      this.prompt(prompts, function (props) {
        options = _.assign(options, props);
        // The complete distro includes callbacks that break when serialized to a file.
        props.drupalDistro = props.drupalDistro.id;
        this.config.set(props);
        addBehaviors();
        done();
      }.bind(this));
    }
  },

  writing: {
    gadget: function () {
      var gadgetOptions = {}
      if (options['usePLS']) {
        gadgetOptions.themeScripts = {
          "compile-theme": "npm run compile",
          "validate": "npm run test"
        };
      }

      this.composeWith('gadget', {
        options: _.assign(options, gadgetOptions)
      },
      {
        local: require.resolve('generator-gadget')
      });
    },
    env: function() {
      if (options['useENV']) {
        this.composeWith('p2-env', { options: options }, {
          local: require.resolve('generator-p2-env')
        });
      }
    },
    pls: function() {
      if (options['usePLS']) {
        this.composeWith('pattern-lab-starter', { options: options }, {
          local: require.resolve('generator-pattern-lab-starter')
        });
      }
    },

    readme: function() {
      if (options['useENV']) {
        var tokens = require('generator-gadget/lib/util').tokens(options);
        // Yeoman's dependencies cannot handle dynamic partial includes.
        // This is not currently used.
        tokens.gadgetPath = path.resolve(require.resolve('generator-gadget'), 'app/templates/README.md');
        tokens = _.merge(options, tokens);

        this.fs.copyTpl(
          this.templatePath('README.md'),
          this.destinationPath('README.md'),
          tokens
        );
      }
    }
  },

  install: function () {
    this.installDependencies({
      skipInstall: this.options['skip-install']
    });
  }
});
<|endoftext|>


<commit_before>'use strict';

// /pengo - respond with random tip text from database
// /pengo [ID] - respond with tip text from database specified by ID
// /pengo rant - resond with special JPEG
// /pengo help - responds with helpful tips on using /pengo commands
// TODO
// /pengo bash [command] - respond with reference from http://man.he.net

// one function to handle all slack commands (subject to change if needed)

const getQuote   = require('./getQuote'),
      getCommand = require('./getCommand');

const pengo =  {

  handleCommand: function(request, response) {

    if (request.token !== process.env.VERIFICATION_TOKEN) {
    return; // if request doesn't have a slack token, abort
    }
    // if /pengo includes following text ([id], rant, help, etc.)
    if (request.body.text) {

      // /pengo [ID]
      if ( isNumeric(request.body.text) ){ // TODO add test if id number is in quote db range ex.(1-20)
        getQuote.byID(request.body.text, function(err, quote) {
          if (err) console.error(err);

          let mainQuote = quote[0].quote;
          let quoteId = quote[0].quote_id;
          let quoteSource = quote[0].source;
          let subQuote = quote[0].subquote;
          let quoteSourceUrl = quote[0].source_href;

          let data = {
            "response_type": "in_channel", // public to the channel
            "text": "*" + mainQuote + "*",
            "attachments": [
              {
                "text": subQuote + "\n" + "<" + quoteSourceUrl + "|" + quoteSource + " #" + quoteId + ">",
                "color": "good",
                "footer": "<https://pengo.herokuapp.com | Get Pengo>" + " | "  + "<https://github.com/peterjmartinson/PengoBot | GitHub>",
                "mrkdwn_in": ["text"]
              }
            ]
          }
          response.send(data);
        });
      }

      // /pengo rant or whatever it's going to be called
      else if (request.body.text === 'rant') {
        let responseURL = "https://pengo.herokuapp.com/rant.png";
        let data = {
          "response_type": "in_channel", // public to the channel
          "attachments": [
            {
              "image_url": responseURL,
              "color": "warning"
            }
          ]
        }
        response.send(data);
      }

      // /pengo help
      else if (request.body.text === 'help'){
        let helpMessage =
        '"/pengo" get a random quote from the Pragmatic Programmer \n' +
        '"/pengo [ID]" you can specifiy a quote by ID number \n' +
        '"/pengo rant" get the most important piece of advice';

        let data = {
          "response_type": "in_channel", // public to the channel
          "attachments": [
            {
              "text": helpMessage,
              "color": "#227722"
            }
          ]
        }
        response.send(data);
      }

// ================ Begin Command Line Help

      else if ( /bash [\w\-\+]+$/mig.test(request.body.text) ) {
        let footerText =
          'You are asking for a command!\n' +
          'The command name is: ' +
          request.body.text.substring(5) +
          '\nBut this function is still in development!';

        let data = {
          "response_type": "ephemeral", // public to the channel
          "attachments": [
            {
              "text": "Whoops!",
              "color": "good",
              "footer": footerText
            }
          ]
        }
        response.send(data);
        // getCommand(request.body.text.substring(5), function(err, man) {
        //   if (err) console.error(err);

        //   let data = {
        //     "response_type": "in_channel", // public to the channel
        //     "attachments": [
        //       {
        //         "text": man.synopsis,
        //         "color": "good",
        //         "footer": footerText
        //       }
        //     ]
        //   }
        //   response.send(data);
        // });
      }

// ================== End Command Line Help

      // /pengo (wrong text)
      else {
        let errorMessage = "Sorry, looks like you typed something in wrong. Try /pengo help.";
        let data = {
          "response_type": "ephemeral", // only visible to user
          "attachments": [
            {
              "text": errorMessage,
              "color": "DANGER DANGER"
            }
          ]
        }
        response.send(data);
      }
    }

    else {
      // /pengo, when response.body.text = ''
      getQuote.atRandom(function(err, quote) {
        if (err) console.error(err);
        // let responseText = quote[0].quote;
        let mainQuote = quote[0].quote;
        let quoteId = quote[0].quote_id;
        let quoteSource = quote[0].source;
        let subQuote = quote[0].subquote;
        let quoteSourceUrl = quote[0].source_href;

        let data = {
          "response_type": "in_channel", // public to the channel
          "text": "*" + mainQuote + "*",
          "attachments": [
            {
              "text": subQuote + "\n" + "<" + quoteSourceUrl + "|" + quoteSource + " #" + quoteId + ">",
              "color": "good",
              "footer": "<https://pengo.herokuapp.com | Get Pengo>" + " | "  + "<https://github.com/peterjmartinson/PengoBot | GitHub>",
              "mrkdwn_in": ["text"]
            }
          ]
        }
        response.send(data);
      });
    }
  }
}

// tests if string is a number
function isNumeric(value) {
    return /^\d+$/.test(value);
}

module.exports = pengo;
<commit_msg>fixed undefined verification token error<commit_after>'use strict';

// /pengo - respond with random tip text from database
// /pengo [ID] - respond with tip text from database specified by ID
// /pengo rant - resond with special JPEG
// /pengo help - responds with helpful tips on using /pengo commands
// TODO
// /pengo bash [command] - respond with reference from http://man.he.net

// one function to handle all slack commands (subject to change if needed)

const getQuote   = require('./getQuote'),
      getCommand = require('./getCommand');

const pengo =  {

  handleCommand: function(request, response) {

    if (request.body.token !== process.env.VERIFICATION_TOKEN) {
    return; // if request doesn't have a slack token, abort
    }
    // if /pengo includes following text ([id], rant, help, etc.)
    if (request.body.text) {

      // /pengo [ID]
      if ( isNumeric(request.body.text) ){ // TODO add test if id number is in quote db range ex.(1-20)
        getQuote.byID(request.body.text, function(err, quote) {
          if (err) console.error(err);

          let mainQuote = quote[0].quote;
          let quoteId = quote[0].quote_id;
          let quoteSource = quote[0].source;
          let subQuote = quote[0].subquote;
          let quoteSourceUrl = quote[0].source_href;

          let data = {
            "response_type": "in_channel", // public to the channel
            "text": "*" + mainQuote + "*",
            "attachments": [
              {
                "text": subQuote + "\n" + "<" + quoteSourceUrl + "|" + quoteSource + " #" + quoteId + ">",
                "color": "good",
                "footer": "<https://pengo.herokuapp.com | Get Pengo>" + " | "  + "<https://github.com/peterjmartinson/PengoBot | GitHub>",
                "mrkdwn_in": ["text"]
              }
            ]
          }
          response.send(data);
        });
      }

      // /pengo rant or whatever it's going to be called
      else if (request.body.text === 'rant') {
        let responseURL = "https://pengo.herokuapp.com/rant.png";
        let data = {
          "response_type": "in_channel", // public to the channel
          "attachments": [
            {
              "image_url": responseURL,
              "color": "warning"
            }
          ]
        }
        response.send(data);
      }

      // /pengo help
      else if (request.body.text === 'help'){
        let helpMessage =
        '"/pengo" get a random quote from the Pragmatic Programmer \n' +
        '"/pengo [ID]" you can specifiy a quote by ID number \n' +
        '"/pengo rant" get the most important piece of advice';

        let data = {
          "response_type": "in_channel", // public to the channel
          "attachments": [
            {
              "text": helpMessage,
              "color": "#227722"
            }
          ]
        }
        response.send(data);
      }

// ================ Begin Command Line Help

      else if ( /bash [\w\-\+]+$/mig.test(request.body.text) ) {
        let footerText =
          'You are asking for a command!\n' +
          'The command name is: ' +
          request.body.text.substring(5) +
          '\nBut this function is still in development!';

        let data = {
          "response_type": "ephemeral", // public to the channel
          "attachments": [
            {
              "text": "Whoops!",
              "color": "good",
              "footer": footerText
            }
          ]
        }
        response.send(data);
        // getCommand(request.body.text.substring(5), function(err, man) {
        //   if (err) console.error(err);

        //   let data = {
        //     "response_type": "in_channel", // public to the channel
        //     "attachments": [
        //       {
        //         "text": man.synopsis,
        //         "color": "good",
        //         "footer": footerText
        //       }
        //     ]
        //   }
        //   response.send(data);
        // });
      }

// ================== End Command Line Help

      // /pengo (wrong text)
      else {
        let errorMessage = "Sorry, looks like you typed something in wrong. Try /pengo help.";
        let data = {
          "response_type": "ephemeral", // only visible to user
          "attachments": [
            {
              "text": errorMessage,
              "color": "DANGER DANGER"
            }
          ]
        }
        response.send(data);
      }
    }

    else {
      // /pengo, when response.body.text = ''
      getQuote.atRandom(function(err, quote) {
        if (err) console.error(err);
        // let responseText = quote[0].quote;
        let mainQuote = quote[0].quote;
        let quoteId = quote[0].quote_id;
        let quoteSource = quote[0].source;
        let subQuote = quote[0].subquote;
        let quoteSourceUrl = quote[0].source_href;

        let data = {
          "response_type": "in_channel", // public to the channel
          "text": "*" + mainQuote + "*",
          "attachments": [
            {
              "text": subQuote + "\n" + "<" + quoteSourceUrl + "|" + quoteSource + " #" + quoteId + ">",
              "color": "good",
              "footer": "<https://pengo.herokuapp.com | Get Pengo>" + " | "  + "<https://github.com/peterjmartinson/PengoBot | GitHub>",
              "mrkdwn_in": ["text"]
            }
          ]
        }
        response.send(data);
      });
    }
  }
}

// tests if string is a number
function isNumeric(value) {
    return /^\d+$/.test(value);
}

module.exports = pengo;
<|endoftext|>


<commit_before>// @flow
import { createStore, applyMiddleware, compose } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { persistStore, autoRehydrate } from 'redux-persist'
import localForage from 'localforage'
import type { Store } from 'types'
import reducer from './reducer'
import mySaga from './sagas'

const whitelist = [
  'Table',
  'ColumnManager',
  'ColumnRanking',
  'ColumnRankingR18',
  'ColumnBookmark',
  'ColumnFollow',
  'ColumnUserIllust',
  'ColumnSearch',
  'Language',
  'LoginModal',
  'ModalManeger',
  'SettingModal',
]

let persistor

export default function configureStore(initialState: Object = {}): Store {
  const middleware = []

  const sagaMiddleware = createSagaMiddleware()
  middleware.push(sagaMiddleware)

  const enhancer = compose(
    applyMiddleware(...middleware),
    autoRehydrate(),
    window.devToolsExtension ? window.devToolsExtension() : f => f
  )

  const store = createStore(reducer, initialState, enhancer)

  sagaMiddleware.run(mySaga)

  persistor = persistStore(store, {
    storage: localForage,
    whitelist,
  })

  if (module.hot) {
    // Enable Webpack hot module replacement for reducers
    module.hot.accept('./reducer', () => {
      const nextRootReducer = require('./reducer')

      // $FlowFixMe
      store.replaceReducer(nextRootReducer)
    })
  }

  return store
}

export function clean() {
  if (persistor) {
    persistor.purge()
  }
}
<commit_msg>Tweak<commit_after>// @flow
import { createStore, applyMiddleware, compose } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { persistStore, autoRehydrate } from 'redux-persist'
import localForage from 'localforage'
import type { Store } from 'types'
import reducer from './reducer'
import mySaga from './sagas'

const whitelist = [
  'Table',
  'ColumnManager',
  'ColumnRanking',
  'ColumnRankingR18',
  'ColumnBookmark',
  'ColumnFollow',
  'ColumnUserIllust',
  'ColumnSearch',
  'Language',
  'LoginModal',
  'ModalManeger',
  'SettingModal',
]

let persistor

export default function configureStore(initialState: Object = {}): Store {
  const middleware = []

  const sagaMiddleware = createSagaMiddleware()
  middleware.push(sagaMiddleware)

  const enhancer = compose(
    applyMiddleware(...middleware),
    autoRehydrate(),
    window.devToolsExtension ? window.devToolsExtension() : f => f
  )

  const store = createStore(reducer, initialState, enhancer)

  sagaMiddleware.run(mySaga)

  persistor = persistStore(store, {
    storage: localForage,
    whitelist,
  })

  if (module.hot) {
    // Enable Webpack hot module replacement for reducers
    module.hot.accept('./reducer', () => {
      const nextRootReducer = require('./reducer')

      store.replaceReducer(nextRootReducer)
    })
  }

  return store
}

export function clean() {
  if (persistor) {
    persistor.purge()
  }
}
<|endoftext|>


<commit_before>const expand     = require('toolz/src/string/expand')
const concurrent = require('toolz/src/async/concurrent')
const readFile   = require('toolz/src/file/readFile')
const writeFile  = require('toolz/src/file/writeFile')
const isOr       = require('toolz/src/lang/isOr')
const display    = require('./display')
const path       = require('path')
const fs         = require('fs')
const assert     = require('assert')

function tmpls (opts, cb) {
  const license = opts.meta.license
  const tmpl = opts.files
  var files = []
  var filePath

  Object.keys(tmpl).forEach(function (name) {
    if (tmpl[name]) {
      if (isOr(name, 'gitignore', 'eslintrc')) return files.push(name)
      if (isOr(name, 'index', 'test')) return files.push(name + '.js')
      if (name === 'package') return files.push(name + '.json')
      if (name === 'readme') return files.push(name.toUpperCase() + '.md')
      if (name === 'travis') return files.push(name + '.yml')
      if (name === 'license') {
        if (license === 'ISC') return files.push(name.toUpperCase() + '-ISC')
        else if (license === 'MIT') return files.push(name.toUpperCase() + '-MIT')
        else return files.push(name.toUpperCase() + '-UN')
      }
    }
  })
  // console.log(files)

  display.heading('Templates')

  concurrent.each(files, function (file, key, next) {
    if (isOr(file, 'gitignore', 'travis.yml', 'eslintrc')) {
      filePath = './.' + file
    } else if (isOr(file, 'LICENSE-MIT', 'LICENSE-ISC', 'LICENSE-UN')) {
      filePath = './' + file.replace(/-(MIT|ISC|UN)/, '')
    } else {
      filePath = './' + file
    }

    return readFile(path.join(__dirname, '../lib/' + file), function (err, res) {
      assert.ifError(err)
      var tmpl = expand(res, opts.meta)
      writeFile(filePath, tmpl, function (err) {
        assert.ifError(err)
        display.event('create:', file, 'white')
        next(null)
      })
    })
  }, function (err, results) {
    assert.ifError(err)
    cb(null)
  })
}

module.exports = tmpls
<commit_msg>refactor: revert back to iterating serially over templates<commit_after>const expand    = require('toolz/src/string/expand')
const iterate   = require('toolz/src/async/iterate')
const readFile  = require('toolz/src/file/readFile')
const writeFile = require('toolz/src/file/writeFile')
const isOr      = require('toolz/src/lang/isOr')
const display   = require('./display')
const path      = require('path')
const fs        = require('fs')
const assert    = require('assert')

function tmpls (opts, cb) {
  const license = opts.meta.license
  const tmpl = opts.files
  var files = []
  var filePath

  Object.keys(tmpl).forEach(function (name) {
    if (tmpl[name]) {
      if (isOr(name, 'gitignore', 'eslintrc')) return files.push(name)
      if (isOr(name, 'index', 'test')) return files.push(name + '.js')
      if (name === 'package') return files.push(name + '.json')
      if (name === 'readme') return files.push(name.toUpperCase() + '.md')
      if (name === 'travis') return files.push(name + '.yml')
      if (name === 'license') {
        if (license === 'ISC') return files.push(name.toUpperCase() + '-ISC')
        else if (license === 'MIT') return files.push(name.toUpperCase() + '-MIT')
        else return files.push(name.toUpperCase() + '-UN')
      }
    }
  })
  // console.log(files)

  display.heading('Templates')

  iterate.each(files, function (file, key, next) {
    if (isOr(file, 'gitignore', 'travis.yml', 'eslintrc')) {
      filePath = './.' + file
    } else if (isOr(file, 'LICENSE-MIT', 'LICENSE-ISC', 'LICENSE-UN')) {
      filePath = './' + file.replace(/-(MIT|ISC|UN)/, '')
    } else {
      filePath = './' + file
    }

    return readFile(path.join(__dirname, '../lib/' + file), function (err, res) {
      assert.ifError(err)
      var tmpl = expand(res, opts.meta)
      writeFile(filePath, tmpl, function (err) {
        assert.ifError(err)
        display.event('create:', file, 'white')
        next(null)
      })
    })
  }, function (err, results) {
    assert.ifError(err)
    cb(null)
  })
}

module.exports = tmpls
<|endoftext|>


<commit_before>export function getPageUrl(rel, response) {
  const link = response.headers.get('link')
  if (!link) return null

  const relLink = link.split(',').find(s => s.indexOf(`rel="${rel}"`) > -1)
  if (!relLink) return null

  return relLink.split(';')[0].trim().slice(1, -1)
}

export function idFromSlugId(slugId) {
  const match = slugId.match(/=([A-Za-z0-9-_]{7,})(\?.*)?$/)
  return match && match[1] || null
}

export function pluralize(count, singular, plural) {
  return count === 1 ? singular : plural && plural || `${singular}s`
}
<commit_msg>new utils for cookies and getFilter<commit_after>export function getPageUrl(rel, response) {
  const link = response.headers.get('link')
  if (!link) return null

  const relLink = link.split(',').find(s => s.indexOf(`rel="${rel}"`) > -1)
  if (!relLink) return null

  return relLink.split(';')[0].trim().slice(1, -1)
}

export function idFromSlugId(slugId) {
  const match = slugId.match(/=([A-Za-z0-9-_]{7,})(\?.*)?$/)
  return match && match[1] || null
}

export function getFilter(pathname) {
  let filter = 'all'
  try {
    filter = pathname.match(/^\/(answered|unanswered)/)[1]
  } catch (_) {}
  return filter
}

export function pluralize(count, singular, plural) {
  return count === 1 ? singular : plural && plural || `${singular}s`
}

export function setCookie(name, value, options = {}) {
  let expires = options.expires

  if (expires && typeof expires === 'number') {
    const d = new Date()
    d.setTime(d.getTime() + expires * 1000)
    expires = options.expires = d
  }

  if (expires && expires.toUTCString)
    options.expires = expires.toUTCString()

  value = encodeURIComponent(value)

  let updatedCookie = `${name}=${value}`

  for (var propName in options) {
    updatedCookie += `; ${propName}`
    const propValue = options[propName]
    if (propValue !== true)
      updatedCookie += `=${propValue}`
  }

  document.cookie = updatedCookie
}

export function getCookie(name) {
  try {
    const matches = document.cookie.match(new RegExp(
      '(?:^|; )' + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + '=([^;]*)'
    ))
    return matches ? decodeURIComponent(matches[1]) : undefined
  } catch (_) {
    return undefined
  }
}
<|endoftext|>


<commit_before>/*
File: flowcell_samples.js
URL: /static/js/flowcell_samples.js
Powers /flowcells/[FcID] - template is run_dir/design/flowcell_samples.html
*/

// Get data attributes
var flowcell = $('#flowcells-js').attr('data-flowcell');

function display_undetermined(lane){
    $("#table_ud_lane_" + lane + ':first').slideToggle();
}

for (var i = 1; i < 9; i++) {
    $("#lanes").append('

Lane ' + i + '

')
};

$.getJSON("/api/v1/flowcell_info2/"+flowcell, function(data) {

    load_running_notes()
    load_links()

    // Fill in the main table with summary information
    var tbody = '
 \
                     Sequencing done	 \
                     ' + data['seqdone'] + '	 \

 \

 \
                     Demultiplex	';
    if(data.hasOwnProperty("demuldone")){
        tbody += 'Done	';
    } else {
        tbody += 'Pending
';
    }
    tbody += "
";
    if(data.hasOwnProperty("plist")){
        tbody += "
 \
            Projects :	\
            " + data["plist"].join(", ") + "	\

";
    }
    $("#fc_info tbody").html(tbody);

    // If demultiplexed, get additional information for each lane
    if(data.hasOwnProperty("demuldone")){

        // First table - Overall lane stats
        for (lid=1; lid<9; lid++){
            var sbody = '';
            if ('lanedata'in data && lid in data['lanedata']){
                sbody = '
 \
                    Total Yield (Mb):	 \
                    ' + nice_numbers(data['lanedata'][lid]['yield']) + '	 \
                    Total clusters :	 \
                    ' + nice_numbers(data['lanedata'][lid]['clustersnb']) + '	 \
                    % bases > Q30:	 \
                    <td class="text-left ';
                q30=data['lanedata'][lid]['overthirty']
                if (q30 < 30) sbody += 'danger';
                else if(q30 < 80) sbody += 'warning';
                else if(q30 < 100) sbody += 'success';
                sbody+='">' + q30 + '%	 \
                    Mean Quality Score:	 \
                    ' + data['lanedata'][lid]['mqs'] + '	 \
                    % perfect barcode :	 \
                    ' + data['lanedata'][lid]['perf'] + '%	';
                if ('phix' in data['lanedata'][lid]){
                   sbody+='% PhiX:	 \
                    ' + data['lanedata'][lid]['phix'] + '%	';
                }
                if ('er_rate' in data['lanedata'][lid]){
                   sbody+='Err. Rate:	 \
                    ' + data['lanedata'][lid]['er_rate'] + '	';
                }

                sbody+='
';
            } else if (data['yields'][lid] != 0){
                    sbody='
Total Yield (Mb):	 \
                    ' + data['yields'][lid]+ '
';
            }
            status = data['seq_qc'][lid];
            if (status !== '0'){
                if(status == 'PASSED') status = 'Passed';
                if(status == 'UNKNOWN') status = 'Unknown';
                if(status == 'FAILED') status = 'Failed';
                sbody+="
 \
                    Sequencing QC:	 \
                    " + status + "	 \

";
            }
            for(i=1; i < 4; i++){
                if(data.hasOwnProperty('err'+i)){
                    sbody += "
 \
                        Read " + i + " Error rate :	 \
                        " + data['err'+i][lid] + "	 \

";
                }
            }
            // Add the table if it has some content
            if(sbody.length > 0){
                $('#lane_'+lid).removeClass('hidden').append(' \

 \

 \
                            ' + sbody + ' \

 \

 ');
            }
        }

        // Second table - Project Samples in each lane
        for (lid in data['lane']){
            if(data['lane'][lid].length == 0){
                continue;
            }
            var lbody='
 \

 \

 \
                                Project Name	 \
                                Sample Name	 \
                                Yield (Mb)	';

            if (data['lane'][lid][0].hasOwnProperty('readsnb')){
                lbody+='# Reads	';
            }else if(data['lane'][lid][0].hasOwnProperty('clustersnb')){
                lbody+='# Clusters	';
            }

            lbody+='% > Q30	 \
                    Barcode	';
            if (data['lane'][lid][0].hasOwnProperty('desc')){
                lbody+='Index description	';
            }
            if (data['lane'][lid][0].hasOwnProperty('lanepc')){
                lbody+='% of the lane	';
            }
            if (data['lane'][lid][0].hasOwnProperty('mqs')){
                lbody+='Mean QualityScore	';
            }
            lbody+='
';

            var total_undetermined_claster_number = 0;

            for (samplerunid in data['lane'][lid]){
                var q30 = parseFloat(data['lane'][lid][samplerunid]['overthirty']);
                if (data['lane'][lid][samplerunid]['SampleName'] == 'Undetermined') {
                    total_undetermined_claster_number = parseInt(data['lane'][lid][samplerunid]['clustersnb'].replace(/,/g, ''));
                }
                lbody += "
 \
                    " + data['lane'][lid][samplerunid]['Project'] + "	 \
                    " + data['lane'][lid][samplerunid]['SampleName'] + '	 \
                    ' + nice_numbers(data['lane'][lid][samplerunid]['yield']) + '	'
                    if (data['lane'][lid][0].hasOwnProperty('readsnb')){
                        lbody+='' + nice_numbers(data['lane'][lid][samplerunid]['readsnb']) + '	'
                    }else if(data['lane'][lid][0].hasOwnProperty('clustersnb')){
                        lbody+='' + nice_numbers(data['lane'][lid][samplerunid]['clustersnb']) + '	'
                    }

                lbody+='<td class="text-right ';
                if (q30 < 30) lbody += 'danger';
                else if(q30 < 80) lbody += 'warning';
                else if(q30 < 100) lbody += 'success';
                lbody += '">' + data['lane'][lid][samplerunid]['overthirty'] + " %	\
                    " + data['lane'][lid][samplerunid]['barcode'] + "	";
                if (data['lane'][lid][0].hasOwnProperty('desc')){
                    lbody+="" + data['lane'][lid][samplerunid]['desc'] + "	";
                }
                if (data['lane'][lid][0].hasOwnProperty('lanepc')){
                    lbody+="" + data['lane'][lid][samplerunid]['lanepc'].toFixed(2) + " %	";
                }
                if (data['lane'][lid][0].hasOwnProperty('mqs')){
                    lbody+="" + data['lane'][lid][samplerunid]['mqs'] + "	";
                }
                lbody += "
";
            }
            lbody += '

';
            $('#lane_'+lid).append(lbody);
            if ('undetermined' in data) {
                var ludtable='
';
                ludtable += "
Total	"+nice_numbers(total_undetermined_claster_number)+"	(100%)	";
                if(data['lane'][lid].length <= 10){
                        for (var s in data['lane'][lid]){
                        if(data['lane'][lid][s]['barcode'] !== 'unknown'){
                            ludtable += ''+data['lane'][lid][s]['barcode']+' mismatches	';
                        }
                    }
                }
                ludtable += "
";

                var button='<button id="ud_button_lane_' +lid + '" class="undetermined-btn btn btn-info btn-sm" \
                           type="button" onclick="display_undetermined(' + lid + ')" >Show Undetermined</button>';
                var keys = [];
                for(var key in data['undetermined'][lid]) keys.push(key);
                var ordered_keys=keys.sort(function(a,b){return data['undetermined'][lid][b]-data['undetermined'][lid][a]});
                for (ud in ordered_keys){
                    // Try to look for exact barcode matches with Ns in
                    // eg. highlight ATTACNNN if ATTACTCG was a barcode for this lane
                    // probably doesn't work with double barcodes and will highlight
                    // potentially unuseful sequences such as NNNNNNNN. Sorry about that.
                    var unmatched = ordered_keys[ud];
                    var hl = '';
                    for (samplerunid in data['lane'][lid]){
                        var bc = data['lane'][lid][samplerunid]['barcode'];
                        // important replacement:
                        // + was recognized as part of regex, which causes js syntax error
                        bc = bc.replace(/\+/g, "-");
                        if (bc == 'unknown') { continue; }
                        bc = '^('+bc.split('').join('|N)(')+'|N)$';
                        if(ordered_keys[ud].match(bc)){
                            hl = ' class="undetermined-highlight"';
                        }
                    }
                    // Make count nice and work out percentage
                    var count = parseInt(data['undetermined'][lid][ordered_keys[ud]]);
                    var percentage = (100 * count/total_undetermined_claster_number).toFixed(2);

                    count = nice_numbers(count);
                    ludtable += "
<samp>"+unmatched+"</samp>	"+count+'	('+percentage+"%)	";
                    if(data['lane'][lid].length <= 10){
                        for (var s in data['lane'][lid]){
                            var bc = data['lane'][lid][s]['barcode'];
                            if(bc !== 'unknown'){
                                var mm = 0;
                                for (var i = 0, len = unmatched.length; i < len; i++) {
                                    if(unmatched[i] != bc[i] && unmatched[i] != 'N' && bc[i] != 'N'){
                                        mm += 1;
                                    }
                                }
                                var fr_matched = (unmatched.length - mm)/unmatched.length;
                                var tdclass = '';
                                if(fr_matched > 0.6){ tdclass = ' class="undetermined-warning"'; }
                                if(fr_matched > 0.8){ tdclass = ' class="undetermined-highlight"'; }
                                ludtable += ''+mm+'	';
                            }
                        }
                    }
                    ludtable += "
";
                }
                ludtable+="
";
                $('#button_lane_'+lid).append(button);
                $('#lane_'+lid).append(ludtable);
            }
            $('#lane_'+lid).removeClass('hidden');
        }

    }

    // Remove the loading text
    $('#loading_spinner').hide();
    $('#page_content').show();
}).error(function(){
    // Flow cell not found - probably
    $('#page_content').html('
Error - Flow Cell Not Found

Oops! Sorry about that, we can\'t find the flow cell '+flowcell+'
');

    // Remove the loading text
    $('#loading_spinner').hide();
    $('#page_content').show();
});
<commit_msg>Flowcell summary page: highlight number of clusters, if over treshold<commit_after>/*
File: flowcell_samples.js
URL: /static/js/flowcell_samples.js
Powers /flowcells/[FcID] - template is run_dir/design/flowcell_samples.html
*/

// Get data attributes
var flowcell = $('#flowcells-js').attr('data-flowcell');

function display_undetermined(lane){
    $("#table_ud_lane_" + lane + ':first').slideToggle();
}

for (var i = 1; i < 9; i++) {
    $("#lanes").append('

Lane ' + i + '

')
};

$.getJSON("/api/v1/flowcell_info2/"+flowcell, function(data) {

    load_running_notes()
    load_links()
    // Fill in the main table with summary information
    var tbody = '
 \
                     Sequencing done	 \
                     ' + data['seqdone'] + '	 \

 \

 \
                     Demultiplex	';
    if(data.hasOwnProperty("demuldone")){
        tbody += 'Done	';
    } else {
        tbody += 'Pending
';
    }
    tbody += "
";
    if(data.hasOwnProperty("plist")){
        tbody += "
 \
            Projects :	\
            " + data["plist"].join(", ") + "	\

";
    }
    $("#fc_info tbody").html(tbody);

    // If demultiplexed, get additional information for each lane
    if(data.hasOwnProperty("demuldone")){
        // tresholds for the number of clusters: depending on the run mode
        var run_mode = data['run_mode'];
        var treshold = '320';
        if (run_mode == 'HiSeq X') {
            treshold = '320';
        } else if (run_mode == 'RapidHighOutput') {
            treshold = '188';
        } else if (run_mode == 'RapidRun') {
            treshold = '114';
        } else if (run_mode == 'HighOutput') {
            treshold = '143';
        }
        // First table - Overall lane stats
        for (lid=1; lid<9; lid++){
            var sbody = '';
            var clusters_class = '';
            if (data['lanedata'][lid]['clustersnb'] < treshold) {
                clusters_class = 'warning';
            } else {
                clusters_class = 'success';
            }
            if ('lanedata'in data && lid in data['lanedata']){
                sbody = '
 \
                    Total Yield (Mb):	 \
                    ' + nice_numbers(data['lanedata'][lid]['yield']) + '	 \
                    Total clusters :	 \
                    '
                            + nice_numbers(data['lanedata'][lid]['clustersnb']) + '	 \
                    % bases > Q30:	 \
                    <td class="text-left ';
                q30=data['lanedata'][lid]['overthirty']
                if (q30 < 30) sbody += 'danger';
                else if(q30 < 80) sbody += 'warning';
                else if(q30 < 100) sbody += 'success';
                sbody+='">' + q30 + '%	 \
                    Mean Quality Score:	 \
                    ' + data['lanedata'][lid]['mqs'] + '	 \
                    % perfect barcode :	 \
                    ' + data['lanedata'][lid]['perf'] + '%	';
                if ('phix' in data['lanedata'][lid]){
                   sbody+='% PhiX:	 \
                    ' + data['lanedata'][lid]['phix'] + '%	';
                }
                if ('er_rate' in data['lanedata'][lid]){
                   sbody+='Err. Rate:	 \
                    ' + data['lanedata'][lid]['er_rate'] + '	';
                }

                sbody+='
';
            } else if (data['yields'][lid] != 0){
                    sbody='
Total Yield (Mb):	 \
                    ' + data['yields'][lid]+ '
';
            }
            status = data['seq_qc'][lid];
            if (status !== '0'){
                if(status == 'PASSED') status = 'Passed';
                if(status == 'UNKNOWN') status = 'Unknown';
                if(status == 'FAILED') status = 'Failed';
                sbody+="
 \
                    Sequencing QC:	 \
                    " + status + "	 \

";
            }
            for(i=1; i < 4; i++){
                if(data.hasOwnProperty('err'+i)){
                    sbody += "
 \
                        Read " + i + " Error rate :	 \
                        " + data['err'+i][lid] + "	 \

";
                }
            }
            // Add the table if it has some content
            if(sbody.length > 0){
                $('#lane_'+lid).removeClass('hidden').append(' \

 \

 \
                            ' + sbody + ' \

 \

 ');
            }
        }

        // Second table - Project Samples in each lane
        for (lid in data['lane']){
            if(data['lane'][lid].length == 0){
                continue;
            }
            var lbody='
 \

 \

 \
                                Project Name	 \
                                Sample Name	 \
                                Yield (Mb)	';

            if (data['lane'][lid][0].hasOwnProperty('readsnb')){
                lbody+='# Reads	';
            }else if(data['lane'][lid][0].hasOwnProperty('clustersnb')){
                lbody+='# Clusters	';
            }

            lbody+='% > Q30	 \
                    Barcode	';
            if (data['lane'][lid][0].hasOwnProperty('desc')){
                lbody+='Index description	';
            }
            if (data['lane'][lid][0].hasOwnProperty('lanepc')){
                lbody+='% of the lane	';
            }
            if (data['lane'][lid][0].hasOwnProperty('mqs')){
                lbody+='Mean QualityScore	';
            }
            lbody+='
';

            var total_undetermined_claster_number = 0;

            for (samplerunid in data['lane'][lid]){
                var q30 = parseFloat(data['lane'][lid][samplerunid]['overthirty']);
                if (data['lane'][lid][samplerunid]['SampleName'] == 'Undetermined') {
                    total_undetermined_claster_number = parseInt(data['lane'][lid][samplerunid]['clustersnb'].replace(/,/g, ''));
                }
                lbody += "
 \
                    " + data['lane'][lid][samplerunid]['Project'] + "	 \
                    " + data['lane'][lid][samplerunid]['SampleName'] + '	 \
                    ' + nice_numbers(data['lane'][lid][samplerunid]['yield']) + '	'
                    if (data['lane'][lid][0].hasOwnProperty('readsnb')){
                        lbody+='' + nice_numbers(data['lane'][lid][samplerunid]['readsnb']) + '	'
                    }else if(data['lane'][lid][0].hasOwnProperty('clustersnb')){
                        lbody+='' + nice_numbers(data['lane'][lid][samplerunid]['clustersnb']) + '	'
                    }

                lbody+='<td class="text-right ';
                if (q30 < 30) lbody += 'danger';
                else if(q30 < 80) lbody += 'warning';
                else if(q30 < 100) lbody += 'success';
                lbody += '">' + data['lane'][lid][samplerunid]['overthirty'] + " %	\
                    " + data['lane'][lid][samplerunid]['barcode'] + "	";
                if (data['lane'][lid][0].hasOwnProperty('desc')){
                    lbody+="" + data['lane'][lid][samplerunid]['desc'] + "	";
                }
                if (data['lane'][lid][0].hasOwnProperty('lanepc')){
                    lbody+="" + data['lane'][lid][samplerunid]['lanepc'].toFixed(2) + " %	";
                }
                if (data['lane'][lid][0].hasOwnProperty('mqs')){
                    lbody+="" + data['lane'][lid][samplerunid]['mqs'] + "	";
                }
                lbody += "
";
            }
            lbody += '

';
            $('#lane_'+lid).append(lbody);
            if ('undetermined' in data) {
                var ludtable='
';
                ludtable += "
Total	"+nice_numbers(total_undetermined_claster_number)+"	(100%)	";
                if(data['lane'][lid].length <= 10){
                        for (var s in data['lane'][lid]){
                        if(data['lane'][lid][s]['barcode'] !== 'unknown'){
                            ludtable += ''+data['lane'][lid][s]['barcode']+' mismatches	';
                        }
                    }
                }
                ludtable += "
";

                var button='<button id="ud_button_lane_' +lid + '" class="undetermined-btn btn btn-info btn-sm" \
                           type="button" onclick="display_undetermined(' + lid + ')" >Show Undetermined</button>';
                var keys = [];
                for(var key in data['undetermined'][lid]) keys.push(key);
                var ordered_keys=keys.sort(function(a,b){return data['undetermined'][lid][b]-data['undetermined'][lid][a]});
                for (ud in ordered_keys){
                    // Try to look for exact barcode matches with Ns in
                    // eg. highlight ATTACNNN if ATTACTCG was a barcode for this lane
                    // probably doesn't work with double barcodes and will highlight
                    // potentially unuseful sequences such as NNNNNNNN. Sorry about that.
                    var unmatched = ordered_keys[ud];
                    var hl = '';
                    for (samplerunid in data['lane'][lid]){
                        var bc = data['lane'][lid][samplerunid]['barcode'];
                        // important replacement:
                        // + was recognized as part of regex, which causes js syntax error
                        bc = bc.replace(/\+/g, "-");
                        if (bc == 'unknown') { continue; }
                        bc = '^('+bc.split('').join('|N)(')+'|N)$';
                        if(ordered_keys[ud].match(bc)){
                            hl = ' class="undetermined-highlight"';
                        }
                    }
                    // Make count nice and work out percentage
                    var count = parseInt(data['undetermined'][lid][ordered_keys[ud]]);
                    var percentage = (100 * count/total_undetermined_claster_number).toFixed(2);

                    count = nice_numbers(count);
                    ludtable += "
<samp>"+unmatched+"</samp>	"+count+'	('+percentage+"%)	";
                    if(data['lane'][lid].length <= 10){
                        for (var s in data['lane'][lid]){
                            var bc = data['lane'][lid][s]['barcode'];
                            if(bc !== 'unknown'){
                                var mm = 0;
                                for (var i = 0, len = unmatched.length; i < len; i++) {
                                    if(unmatched[i] != bc[i] && unmatched[i] != 'N' && bc[i] != 'N'){
                                        mm += 1;
                                    }
                                }
                                var fr_matched = (unmatched.length - mm)/unmatched.length;
                                var tdclass = '';
                                if(fr_matched > 0.6){ tdclass = ' class="undetermined-warning"'; }
                                if(fr_matched > 0.8){ tdclass = ' class="undetermined-highlight"'; }
                                ludtable += ''+mm+'	';
                            }
                        }
                    }
                    ludtable += "
";
                }
                ludtable+="
";
                $('#button_lane_'+lid).append(button);
                $('#lane_'+lid).append(ludtable);
            }
            $('#lane_'+lid).removeClass('hidden');
        }

    }

    // Remove the loading text
    $('#loading_spinner').hide();
    $('#page_content').show();
}).error(function(){
    // Flow cell not found - probably
    $('#page_content').html('
Error - Flow Cell Not Found

Oops! Sorry about that, we can\'t find the flow cell '+flowcell+'
');

    // Remove the loading text
    $('#loading_spinner').hide();
    $('#page_content').show();
});
<|endoftext|>


<commit_before>angular.module('pascalprecht.translate')
/**
 * @ngdoc directive
 * @name pascalprecht.translate.directive:translateNamespace
 * @restrict A
 *
 * @description
 * Translates given translation id either through attribute or DOM content.
 * Internally it uses `translate` filter to translate translation id. It possible to
 * pass an optional `translate-values` object literal as string into translation id.
 *
 * @param {string=} translate namespace name which could be either string or interpolated string.
 *
 * @example
   <example module="ngView">
    <file name="index.html">

.HEADERS.TITLE

.HEADERS.WELCOME

.TITLE

.WELCOME

    </file>
    <file name="script.js">
      angular.module('ngView', ['pascalprecht.translate'])

      .config(function ($translateProvider) {

        $translateProvider.translations('en',{
          'TRANSLATION_ID': 'Hello there!',
          'CONTENT': {
            'HEADERS': {
                TITLE: 'Title'
            }
          },
          'CONTENT.HEADERS.WELCOME': 'Welcome'
        }).preferredLanguage('en');

      });

    </file>
   </example>
 */
.directive('translateNamespace', translateNamespaceDirective);

function translateNamespaceDirective() {

  'use strict';

  return {
    restrict: 'A',
    scope: true,
    compile: function () {
      return {
        pre: function (scope, iElement, iAttrs) {
          scope.translateNamespace = _getTranslateNamespace(scope);

          if (scope.translateNamespace && iAttrs.translateNamespace.charAt(0) === '.') {
            scope.translateNamespace += iAttrs.translateNamespace;
          } else {
            scope.translateNamespace = iAttrs.translateNamespace;
          }
        }
      };
    }
  };
}

/**
 * Returns the scope's namespace.
 * @private
 * @param scope
 * @returns {string}
 */
function _getTranslateNamespace(scope) {
  'use strict';
  if (scope.translateNamespace) {
    return scope.translateNamespace;
  }
  if (scope.$parent) {
    return _getTranslateNamespace(scope.$parent);
  }
}

translateNamespaceDirective.displayName = 'translateNamespaceDirective';
<commit_msg>docs: fix minor typo in translate-namespace.js<commit_after>angular.module('pascalprecht.translate')
/**
 * @ngdoc directive
 * @name pascalprecht.translate.directive:translateNamespace
 * @restrict A
 *
 * @description
 * Translates given translation id either through attribute or DOM content.
 * Internally it uses `translate` filter to translate translation id. It is possible to
 * pass an optional `translate-values` object literal as string into translation id.
 *
 * @param {string=} translate namespace name which could be either string or interpolated string.
 *
 * @example
   <example module="ngView">
    <file name="index.html">

.HEADERS.TITLE

.HEADERS.WELCOME

.TITLE

.WELCOME

    </file>
    <file name="script.js">
      angular.module('ngView', ['pascalprecht.translate'])

      .config(function ($translateProvider) {

        $translateProvider.translations('en',{
          'TRANSLATION_ID': 'Hello there!',
          'CONTENT': {
            'HEADERS': {
                TITLE: 'Title'
            }
          },
          'CONTENT.HEADERS.WELCOME': 'Welcome'
        }).preferredLanguage('en');

      });

    </file>
   </example>
 */
.directive('translateNamespace', translateNamespaceDirective);

function translateNamespaceDirective() {

  'use strict';

  return {
    restrict: 'A',
    scope: true,
    compile: function () {
      return {
        pre: function (scope, iElement, iAttrs) {
          scope.translateNamespace = _getTranslateNamespace(scope);

          if (scope.translateNamespace && iAttrs.translateNamespace.charAt(0) === '.') {
            scope.translateNamespace += iAttrs.translateNamespace;
          } else {
            scope.translateNamespace = iAttrs.translateNamespace;
          }
        }
      };
    }
  };
}

/**
 * Returns the scope's namespace.
 * @private
 * @param scope
 * @returns {string}
 */
function _getTranslateNamespace(scope) {
  'use strict';
  if (scope.translateNamespace) {
    return scope.translateNamespace;
  }
  if (scope.$parent) {
    return _getTranslateNamespace(scope.$parent);
  }
}

translateNamespaceDirective.displayName = 'translateNamespaceDirective';
<|endoftext|>


<commit_before>window.cls || (window.cls = {});

/**
  * @constructor
  * @extends InspectableJSObject
  */

cls.Watches = function(view)
{
  /* interface */
  /* inherits from InspectableJSObject */

  this.add_property = function(key, uid){};

  this.remove_property = function(uid){};

  this.update_watches = function(){};

  /* constants */

  const
  STATUS = 0,
  PROPERTY_LIST = 1,
  NAME = 0,
  TYPE = 1,
  VALUE = 2,
  OBJECT_VALUE = 3,
  OBJECT_ID = 0,
  IS_EDITABLE = 5,
  UID = 6,
  IS_UPDATED = 7;

  /* private */

  /*
    example property list
    [
      ["a", "string", "a"],
      ["b", "string", "b"],
      ["activeElement", "object", null, [26, 0, "object", 41, "HTMLButtonElement"]],
      ["alinkColor", "string", ""],
      ["all", "object", null, [27, 1, "object", 42, "HTMLCollection"]],
      ...
    ]
  */

  this._update_prop = function(key, uid, update_list)
  {
    var frame = window.stop_at.getSelectedFrame() ||
                {
                  runtime_id: window.runtimes.getSelectedRuntimeId(),
                  thread_id: 0,
                  index: 0,
                };
    this._rt_id = frame.runtime_id;
    var tag = this._tagman.set_callback(this,
                                        this._handle_update_prop,
                                        [key, uid, update_list]);
    this._esdb.requestEval(tag, [frame.runtime_id,
                                 frame.thread_id,
                                 frame.index,
                                 key, [["dummy", 0]]]);
  }

  this._handle_update_prop = function(status, message, key, uid, update_list)
  {
    /*
      examples
      ["completed","object",null,[2,0,"object",3,"HTMLDocument"]]
      ["unhandled-exception","object",null,[26,0,"object",27,"Error"]]
    */

    if (status)
    {
      opera.postError("Watching " + key + " failed.");
    }
    else
    {
      update_list[uid] = true;
      var prop_list = this._obj_map.watches[0][PROPERTY_LIST];
      for (var i = 0; i < prop_list.length && prop_list[i][UID] != uid; i++);
      if (prop_list[i])
      {
        if (message[STATUS] == "completed")
        {
          prop_list[i][TYPE] = message[TYPE];
          prop_list[i][VALUE] = message[VALUE];
          prop_list[i][OBJECT_VALUE] = message[OBJECT_VALUE];
        }
        else
        {
          prop_list[i][TYPE] = "error";
          prop_list[i][VALUE] = "Error";
        }
      }
      else
      {
        opera.postError("Missing property in watches.");
      }
      var all_updated = true;
      for (var check in update_list)
      {
        all_updated = all_updated && update_list[check];
      }
      if (all_updated)
      {
        this._view.update();
      }
    }
  };

  this._get_uid = (function()
  {
    var uid = 0;
    return function() {return ++uid;};
  })();

  this._super_init = this._init;

  this._init = function(view)
  {
    this._super_init(0, "watches");
    this._set_initial_values();
    this._esdb = window.services['ecmascript-debugger'];
    this._tagman = window.tag_manager;
    this._view = view;
  };

  this._set_initial_values = function()
  {
    this._obj_map =
    {
      "0": [[["watches"]]],
      "watches": [[[], []]]
    };
    this._expand_tree =
    {
      "object_id": 0,
      "protos": {"0": {"": {"object_id": "watches"}}}
    };
  };

  /* implementation */

  this.add_property = function(key, uid)
  {
    var prop = [];
    var prop_list = this._obj_map.watches[0][PROPERTY_LIST];
    for (var i = 0; i < prop_list.length && prop_list[i][UID] != uid; i++);
    prop[NAME] = key;
    prop[IS_EDITABLE] = true;
    prop[UID] = prop_list[i] && prop_list[i][UID] || this._get_uid();
    prop_list[i] = prop;
    var update_list = {};
    update_list[prop[UID]] = false;
    this._update_prop(key, prop[UID], update_list);
  };

  this.remove_property = function(uid)
  {
    var prop_list = this._obj_map.watches[0][PROPERTY_LIST];
    for (var i = 0; i < prop_list.length && prop_list[i][UID] != uid; i++);
    if (prop_list[i])
    {
      var prop = prop_list[i];
      if (prop[TYPE] == "object")
      {
        this.collapse([[prop[NAME], prop[OBJECT_VALUE][OBJECT_ID], 0]]);
      }
      prop_list.splice(i, 1);
    }
    this._view.update();
  };

  this.remove_all_properties = function()
  {
    this._set_initial_values();
    this._view.update();
  };

  this.update_watches = function()
  {
    var update_list = {};
    this._obj_map.watches[0][PROPERTY_LIST].forEach(function(prop)
    {
      if (prop[TYPE] == "object")
      {
        this.collapse([[prop[NAME], prop[OBJECT_VALUE][OBJECT_ID], 0]]);
      }
      update_list[prop[UID]] = false;
      this._update_prop(prop[NAME], prop[UID], update_list);
    }, this);
  };

  /* initialisation */

  this._init(view);

};

cls.Watches.prototype = cls.EcmascriptDebugger["6.0"].InspectableJSObject.prototype;
<commit_msg>Fix for DFL-3083, Can't remove invalid watches.<commit_after>window.cls || (window.cls = {});

/**
  * @constructor
  * @extends InspectableJSObject
  */

cls.Watches = function(view)
{
  /* interface */
  /* inherits from InspectableJSObject */

  this.add_property = function(key, uid){};

  this.remove_property = function(uid){};

  this.update_watches = function(){};

  /* constants */

  var ERROR_MSG = 0;
  var STATUS = 0;
  var PROPERTY_LIST = 1;
  var NAME = 0;
  var TYPE = 1;
  var VALUE = 2;
  var OBJECT_VALUE = 3;
  var OBJECT_ID = 0;
  var IS_EDITABLE = 5;
  var UID = 6;
  var IS_UPDATED = 7;

  /* private */

  /*
    example property list
    [
      ["a", "string", "a"],
      ["b", "string", "b"],
      ["activeElement", "object", null, [26, 0, "object", 41, "HTMLButtonElement"]],
      ["alinkColor", "string", ""],
      ["all", "object", null, [27, 1, "object", 42, "HTMLCollection"]],
      ...
    ]
  */

  this._update_prop = function(key, uid, update_list)
  {
    var frame = window.stop_at.getSelectedFrame() ||
                {
                  runtime_id: window.runtimes.getSelectedRuntimeId(),
                  thread_id: 0,
                  index: 0,
                };
    this._rt_id = frame.runtime_id;
    var tag = this._tagman.set_callback(this,
                                        this._handle_update_prop,
                                        [key, uid, update_list]);
    this._esdb.requestEval(tag, [frame.runtime_id,
                                 frame.thread_id,
                                 frame.index,
                                 key, [["dummy", 0]]]);
  }

  this._handle_update_prop = function(status, message, key, uid, update_list)
  {
    /*
      examples
      ["completed","object",null,[2,0,"object",3,"HTMLDocument"]]
      ["unhandled-exception","object",null,[26,0,"object",27,"Error"]]
    */

    update_list[uid] = true;
    var prop_list = this._obj_map.watches[0][PROPERTY_LIST];
    for (var i = 0; i < prop_list.length && prop_list[i][UID] != uid; i++);
    if (prop_list[i])
    {
      if (status)
      {
        prop_list[i][TYPE] = "error";
        prop_list[i][VALUE] = message[ERROR_MSG].contains("Syntax")
                            ? "Syntax error"
                            : "Error";
      }
      else if (message[STATUS] == "completed")
      {
        prop_list[i][TYPE] = message[TYPE];
        prop_list[i][VALUE] = message[VALUE];
        prop_list[i][OBJECT_VALUE] = message[OBJECT_VALUE];
      }
      else
      {
        prop_list[i][TYPE] = "error";
        prop_list[i][VALUE] = "Error";
      }
    }
    else
    {
      opera.postError("Missing property in watches.");
    }
    var all_updated = true;
    for (var check in update_list)
    {
      all_updated = all_updated && update_list[check];
    }
    if (all_updated)
    {
      this._view.update();
    }
  };

  this._get_uid = (function()
  {
    var uid = 0;
    return function() {return ++uid;};
  })();

  this._super_init = this._init;

  this._init = function(view)
  {
    this._super_init(0, "watches");
    this._set_initial_values();
    this._esdb = window.services['ecmascript-debugger'];
    this._tagman = window.tag_manager;
    this._view = view;
  };

  this._set_initial_values = function()
  {
    this._obj_map =
    {
      "0": [[["watches"]]],
      "watches": [[[], []]]
    };
    this._expand_tree =
    {
      "object_id": 0,
      "protos": {"0": {"": {"object_id": "watches"}}}
    };
  };

  /* implementation */

  this.add_property = function(key, uid)
  {
    var prop = [];
    var prop_list = this._obj_map.watches[0][PROPERTY_LIST];
    for (var i = 0; i < prop_list.length && prop_list[i][UID] != uid; i++);
    prop[NAME] = key;
    prop[IS_EDITABLE] = true;
    prop[UID] = prop_list[i] && prop_list[i][UID] || this._get_uid();
    prop_list[i] = prop;
    var update_list = {};
    update_list[prop[UID]] = false;
    this._update_prop(key, prop[UID], update_list);
  };

  this.remove_property = function(uid)
  {
    var prop_list = this._obj_map.watches[0][PROPERTY_LIST];
    for (var i = 0; i < prop_list.length && prop_list[i][UID] != uid; i++);
    if (prop_list[i])
    {
      var prop = prop_list[i];
      if (prop[TYPE] == "object")
      {
        this.collapse([[prop[NAME], prop[OBJECT_VALUE][OBJECT_ID], 0]]);
      }
      prop_list.splice(i, 1);
    }
    this._view.update();
  };

  this.remove_all_properties = function()
  {
    this._set_initial_values();
    this._view.update();
  };

  this.update_watches = function()
  {
    var update_list = {};
    this._obj_map.watches[0][PROPERTY_LIST].forEach(function(prop)
    {
      if (prop[TYPE] == "object")
      {
        this.collapse([[prop[NAME], prop[OBJECT_VALUE][OBJECT_ID], 0]]);
      }
      update_list[prop[UID]] = false;
      this._update_prop(prop[NAME], prop[UID], update_list);
    }, this);
  };

  /* initialisation */

  this._init(view);

};

cls.Watches.prototype = cls.EcmascriptDebugger["6.0"].InspectableJSObject.prototype;
<|endoftext|>


<commit_before>// ==UserScript==
// @name        E-Hentai Torrent Modifier
// @namespace   vonthar
// @version     1.0
// @description Modifies ehtracker torrent files to include gallery url in their comment field and shows torrent links on gallery page.
// @include     *://exhentai.org/g/*
// @include     *://g.e-hentai.org/g/*
// @author      vonthar
// @license     MPL-2.0
// @icon        data:image/gif;base64,R0lGODlhLAAwAPQfAHFxcJKSki4uLtLQ0P7+/uHi4ru7u1hYWEVFRfX19dKjpAgICKSiomRWV1RHSIqIh2hoZ0dURzg4ODEiIj4+PjxFOxsaG1NTU0U8RGJiYn+Af11dXU1NTSQkJOzs7JOSkyH5BAUAAB8ALAAAAAAsADAAAAX/4CeOZGl+2QYA2+m+8AdcUMAMg2FoR+y/FI2h4PEEOBeAYQD4OUcNhieRIBQ6FqxAw2w+YZeOgZAoEgwCiUDQEXu8XxOgswgQitSzZS2xcBIFTHEmCxYSHndVBFZqEnQMd4EQgyNrCwCJBDlLG3sWdlREBnBxFxYLD3dWdB1pFhaQZGUFBAGUIp12iAwLCBoPG1gDsrMFBaSDkLsLFxoAFBYai1ShAwkMtyJSiAMdCBQC0YvEmkMeGtkB1osIC8xj43kJSwXX2R+piO0GiuS1scYm3XpwYx4tRWUUATAEQdGDbELGaaKViQEdAaYwaWpB6QGiehNlEbiwoEMGCCgt/4xJgOwJgAcPNMYjoGEBBwAZMhwIBoEMtlsLJowxs4iXLwgZLiBAIAABGUG3ODAw8IBWAQPQAjw4oPRbmlAPswlAJYoJBAYQLnBYqkYALQ8/gaYic0ZAAA0XukpQ02GYh1HZFlCckkHCAwgH1u6lsEBAlUAtnWjoMM3DAAoZWCSmIAFcnUUeCqA7IfDFhgygEwSQAACxUs5jpY0zQuoAmzWRRVAARZgCYq4I9r4aJhEtCQsQOFAI80KA3wQDEHDYcAG4o14SFxnIMAKAgwcOGjww6QLBlCkBBGw4sDM4m8/ZC5QWQIFpgwZ7T29AiRRjJA8QqLfTBeC0YQFFEh0jAv8AAnAgQXCdYQZBazlBsEBPxgywFnsHFDjWAdmB5sUzF9DHlgDsbaDiTgtIM0ABDFCQIlOt0GFHiOcsKEFOGNS3Rl5qcSBkHdAdQ4GKNLZhY4h3hOWgAgM00FQaS1W51AIMGIPVBRlwoAUFHHCFInGzjfaBBAwo4MCDi1Hg5lKMZXkOAoW1gsABOa1nygLwLCLaCBLc96CbnHXmCBawzNNhBxJMlwKHHLaC0AC2iLBXU0spZ6iBp4zRTlN5QspeXhzARwADYX3g4Jv1feOIAxw0daGFO+q0k62icoAcXQAsUMEIjLLRSiPfYYAABIxZcBKkK56WQl4IWLCBdq2AA0OpoBl00EAA4CCw3np45lShTkJKsABqCZD0DQbXwiaBA5x14KizKOVJnWKXnLoAZxPEcOQawbmJEX8nQQDuvcphsUQbAvQbAxIOOCDdYuLytyISirFBEHdPbCCBAmoW6u1OKurZFWzylvbEBQ4MoAB+nMm44qhsSTABFr/eYgHIxsaMhFoIYMBXKxHcIwKjGGD0poPhMBpBD0aXUK2bbdUXNQyxrkHB1SKEAAA7
// @homepageURL https://github.com/vonthar/GM_ehentai-torrent-modifier
// @supportURL  https://github.com/vonthar/GM_ehentai-torrent-modifier/issues
// @downloadURL https://raw.githubusercontent.com/vonthar/GM_ehentai-torrent-modifier/master/src/ehentai-torrent-modifier.user.js
// @require     https://raw.githubusercontent.com/vonthar/GM_ehentai-torrent-modifier/master/lib/parsetorrent.bundle.js
// @run-at      document-start
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// ==/UserScript==

var gallery;
document.addEventListener("DOMContentLoaded", function () {
  if (gallery.torrents) {
    insertTorrentTable();
  }
  gallery.name = document.getElementById("gn").textContent;
  gallery.uploader = document.getElementById("gdn").textContent;
  gallery.posted = new Date(document.getElementById("gdd").getElementsByClassName("gdt2")[0].textContent);
});
getTorrentInfo();

function getTorrentInfo() {
  var tokens = getTokens();
  gallery = {
    url: tokens.base + "g/" + tokens.gid + "/" + tokens.token + "/",
  };
  GM_xmlhttpRequest({
    url: url = tokens.base + "gallerytorrents.php?gid=" + tokens.gid + "&t=" + tokens.token,
    method: "GET",
    onload: function (response) {
      var xmlDocument = new DOMParser().parseFromString(response.responseText, "text/xml");
      gallery.torrents = parseTorrentInfo(xmlDocument);
      if (gallery.name) {
        insertTorrentTable();
      }
    },
    onerror: function (response) {
      console.error(response.statusText);
    }
  });
}

function getTokens() {
  var matches = window.location.href.match(/(.+\/)g\/(\d+)\/([^\/]+)/);
  return {
    base: matches[1],
    gid: matches[2],
    token: matches[3]
  };
}

function getDataUri(buffer, mimeType) {
  buffer = new Buffer(buffer);
  if (!mimeType) {
    mimeType = "text/plain";
  }
  return "data:" + mimeType + ";base64," + buffer.toString("base64");
}

function browserDownload(url, name) {
  var a = document.createElement("a");
  a.href = url;
  if (name) {
    a.download = name;
  }
  a.dispatchEvent(new MouseEvent("click"));
}

function parseSize(value) {
  var matches = value.match(/(\d+) (\S+)/);
  if (matches) {
    value = parseInt(matches[1], 10);
    switch (matches[2]) {
      case "B":
        return value;
      case "KB":
        return value * 1000;
      case "MB":
        return value * 1000000;
      case "GB":
        return value * 1000000000;
    }
  }
  return value;
}

function parseTorrentInfo(xmlDocument) {
  var property = />(.*):<\/span>\s*(.*)$/;
  var torrents = [];
  var forms = xmlDocument.getElementById("torrentinfo").getElementsByTagName("form");
  var i, j, link, fields, matches;
  for (i = 0; i < forms.length - 1; i++) {
    link = forms[i].getElementsByTagName("a")[0];
    torrents[i] = {
      url: link.href,
      name: link.innerText
    };
    fields = forms[i].getElementsByTagName("td");
    for (var j = 0; j < fields.length; j++) {
      matches = fields[j].innerHTML.match(property);
      if (matches) {
        torrents[i][matches[1].toLocaleLowerCase()] = matches[2];
      }
    }
  }
  return torrents;
}

function getTorrentsSorted() {
  var key = GM_getValue("sortKey");
  var order = GM_getValue("sortOrder", "desc");
  switch (key) {
    case "size":
      if (order === "desc") {
        return gallery.torrents.sort(function (a, b) {
          return parseSize(b.size) - parseSize(a.size);
        });
      }
    default:
      key = "downloads";
    case "seeds":
    case "peers":
    case "downloads":
      if (order === "desc") {
        return gallery.torrents.sort(function (a, b) {
          return parseInt(b[key], 10) - parseInt(a[key], 10);
        });
      }
      return gallery.torrents.sort(function (a, b) {
        return parseInt(a[key], 10) - parseInt(b[key], 10);
      });
    case "posted":
    case "uploader":
      if (order === "desc") {
        return gallery.torrents.sort(function (a, b) {
          return String(b.posted).localeCompare(a.posted);
        });
      }
      return gallery.torrents.sort(function (a, b) {
        return String(a.posted).localeCompare(b.posted);
      });
  }
}

function updateSortOrder(event) {
  event.preventDefault();
  var key = GM_getValue("sortKey", "downloads");
  var value;
  switch (event.target.textContent) {
    case "DLs":
      value = "downloads";
      break;
    case "Added":
      value = "posted";
      break;
    default:
      value = name.toLowerCase();
  }
  if (value === key) {
    if (GM_getValue("sortOrder") === "asc") {
      GM_setValue("sortOrder", "desc");
    }
    else {
      GM_setValue("sortOrder", "asc");
    }
  }
  else {
    GM_setValue("sortKey", value);
    GM_setValue("sortOrder", "desc");
  }
  var table = document.getElementById("torrentinfo");
  table.parentNode.replaceChild(createTable(), table);
}

function downloadTorrent(event) {
  if (!parseTorrent) {
    return;
  }
  event.preventDefault();
  var url = event.target.href;
  GM_xmlhttpRequest({
    url: url,
    method: "GET",
    overrideMimeType: "application/x-bittorrent; charset=x-user-defined",
    onload: function (response) {
      var data = new Buffer(response.responseText, "binary");
      if (!data.length) {
        browserDownload(url);
        return;
      }
      var torrent = parseTorrent(data);
      torrent.created = gallery.posted;
      torrent.createdBy = gallery.uploader;
      torrent.comment = gallery.url;
      //torrent.name = gallery.name;
      var uri = getDataUri(parseTorrent.toTorrentFile(torrent), "application/x-bittorrent");
      browserDownload(uri, gallery.name + ".torrent");
    },
    onerror: function () {
      browserDownload(url);
    }
  });
}

function insertTorrentTable() {
  if (gallery.torrents.length) {
    var wrapper = document.createElement("div");
    wrapper.className = "gm";
    wrapper.appendChild(createTable());
    var asm = document.getElementById("asm");
    asm.parentNode.insertBefore(wrapper, asm);
  }
}

function createTable() {
  var header = document.createElement("tr");
  header.appendChild(createLabel("Added", 95));
  var elem = document.createElement("th");
  elem.textContent = "Torrent Name";
  header.appendChild(elem);
  header.appendChild(createLabel("Size", 70));
  header.appendChild(createLabel("Seeds", 35));
  header.appendChild(createLabel("Peers", 35));
  header.appendChild(createLabel("DLs", 40));
  header.appendChild(createLabel("Uploader", 90));

  var tbody = document.createElement("tbody");
  tbody.append(header);
  var torrents = getTorrentsSorted();
  for (var i = 0; i < torrents.length; i++) {
    tbody.appendChild(createRow(torrents[i], i % 2));
  }

  var table = document.createElement("table");
  table.id = "torrentinfo";
  table.className = "itg";
  table.style = "border:0; padding:0; margin:10px 8px; min-width:0; max-width:calc(100% - 16px); outline:1px solid black";
  table.appendChild(tbody);
  return table;
}

function createLabel(name, width) {
  var a = document.createElement("a");
  a.textContent = name;
  a.href = "#";
  a.onclick = updateSortOrder;
  var elem = document.createElement("th");
  elem.style = "text-align:center; width:" + width + "px";
  elem.appendChild(a);
  return elem;
}

function createRow(torrent, mod) {
  var row = document.createElement("tr");
  row.className = "gtr" + mod;
  row.appendChild(createField(torrent.posted, "width:102px; white-space:nowrap"));
  var a = document.createElement("a");
  a.href = torrent.url;
  a.textContent = torrent.name;
  a.onclick = downloadTorrent;
  var div = document.createElement("div");
  div.style = "height:15px; min-width:300px; max-width:600px; overflow:hidden";
  div.appendChild(a);
  var td = document.createElement("td");
  td.className = "itd";
  td.appendChild(div);
  row.appendChild(td);
  row.appendChild(createField(torrent.size, "text-align:right; width:70px; white-space:nowrap"));
  row.appendChild(createField(torrent.seeds, "text-align:right; width:30px"));
  row.appendChild(createField(torrent.peers, "text-align:right; width:30px"));
  row.appendChild(createField(torrent.downloads, "text-align:right; width:40px"));
  row.appendChild(createField(torrent.uploader, "width:90px"));
  return row;
}

function createField(name, style) {
  var elem = document.createElement("td");
  elem.className = "itd";
  elem.textContent = name;
  elem.style = style;
  return elem;
}

<commit_msg>Minor fix<commit_after>// ==UserScript==
// @name        E-Hentai Torrent Modifier
// @namespace   vonthar
// @version     1.0
// @description Modifies ehtracker torrent files to include gallery url in their comment field and shows torrent links on gallery page.
// @include     *://exhentai.org/g/*
// @include     *://g.e-hentai.org/g/*
// @author      vonthar
// @license     MPL-2.0
// @icon        data:image/gif;base64,R0lGODlhLAAwAPQfAHFxcJKSki4uLtLQ0P7+/uHi4ru7u1hYWEVFRfX19dKjpAgICKSiomRWV1RHSIqIh2hoZ0dURzg4ODEiIj4+PjxFOxsaG1NTU0U8RGJiYn+Af11dXU1NTSQkJOzs7JOSkyH5BAUAAB8ALAAAAAAsADAAAAX/4CeOZGl+2QYA2+m+8AdcUMAMg2FoR+y/FI2h4PEEOBeAYQD4OUcNhieRIBQ6FqxAw2w+YZeOgZAoEgwCiUDQEXu8XxOgswgQitSzZS2xcBIFTHEmCxYSHndVBFZqEnQMd4EQgyNrCwCJBDlLG3sWdlREBnBxFxYLD3dWdB1pFhaQZGUFBAGUIp12iAwLCBoPG1gDsrMFBaSDkLsLFxoAFBYai1ShAwkMtyJSiAMdCBQC0YvEmkMeGtkB1osIC8xj43kJSwXX2R+piO0GiuS1scYm3XpwYx4tRWUUATAEQdGDbELGaaKViQEdAaYwaWpB6QGiehNlEbiwoEMGCCgt/4xJgOwJgAcPNMYjoGEBBwAZMhwIBoEMtlsLJowxs4iXLwgZLiBAIAABGUG3ODAw8IBWAQPQAjw4oPRbmlAPswlAJYoJBAYQLnBYqkYALQ8/gaYic0ZAAA0XukpQ02GYh1HZFlCckkHCAwgH1u6lsEBAlUAtnWjoMM3DAAoZWCSmIAFcnUUeCqA7IfDFhgygEwSQAACxUs5jpY0zQuoAmzWRRVAARZgCYq4I9r4aJhEtCQsQOFAI80KA3wQDEHDYcAG4o14SFxnIMAKAgwcOGjww6QLBlCkBBGw4sDM4m8/ZC5QWQIFpgwZ7T29AiRRjJA8QqLfTBeC0YQFFEh0jAv8AAnAgQXCdYQZBazlBsEBPxgywFnsHFDjWAdmB5sUzF9DHlgDsbaDiTgtIM0ABDFCQIlOt0GFHiOcsKEFOGNS3Rl5qcSBkHdAdQ4GKNLZhY4h3hOWgAgM00FQaS1W51AIMGIPVBRlwoAUFHHCFInGzjfaBBAwo4MCDi1Hg5lKMZXkOAoW1gsABOa1nygLwLCLaCBLc96CbnHXmCBawzNNhBxJMlwKHHLaC0AC2iLBXU0spZ6iBp4zRTlN5QspeXhzARwADYX3g4Jv1feOIAxw0daGFO+q0k62icoAcXQAsUMEIjLLRSiPfYYAABIxZcBKkK56WQl4IWLCBdq2AA0OpoBl00EAA4CCw3np45lShTkJKsABqCZD0DQbXwiaBA5x14KizKOVJnWKXnLoAZxPEcOQawbmJEX8nQQDuvcphsUQbAvQbAxIOOCDdYuLytyISirFBEHdPbCCBAmoW6u1OKurZFWzylvbEBQ4MoAB+nMm44qhsSTABFr/eYgHIxsaMhFoIYMBXKxHcIwKjGGD0poPhMBpBD0aXUK2bbdUXNQyxrkHB1SKEAAA7
// @homepageURL https://github.com/vonthar/GM_ehentai-torrent-modifier
// @supportURL  https://github.com/vonthar/GM_ehentai-torrent-modifier/issues
// @downloadURL https://raw.githubusercontent.com/vonthar/GM_ehentai-torrent-modifier/master/src/ehentai-torrent-modifier.user.js
// @require     https://raw.githubusercontent.com/vonthar/GM_ehentai-torrent-modifier/master/lib/parsetorrent.bundle.js
// @run-at      document-start
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// ==/UserScript==

var gallery;
document.addEventListener("DOMContentLoaded", function () {
  if (gallery.torrents) {
    insertTorrentTable();
  }
  gallery.name = document.getElementById("gn").textContent;
  gallery.uploader = document.getElementById("gdn").textContent;
  gallery.posted = new Date(document.getElementById("gdd").getElementsByClassName("gdt2")[0].textContent);
});
getTorrentInfo();

function getTorrentInfo() {
  var tokens = getTokens();
  gallery = {
    url: tokens.base + "g/" + tokens.gid + "/" + tokens.token + "/",
  };
  GM_xmlhttpRequest({
    url: url = tokens.base + "gallerytorrents.php?gid=" + tokens.gid + "&t=" + tokens.token,
    method: "GET",
    onload: function (response) {
      var xmlDocument = new DOMParser().parseFromString(response.responseText, "text/xml");
      gallery.torrents = parseTorrentInfo(xmlDocument);
      if (gallery.name) {
        insertTorrentTable();
      }
    },
    onerror: function (response) {
      console.error(response.statusText);
    }
  });
}

function getTokens() {
  var matches = window.location.href.match(/(.+\/)g\/(\d+)\/([^\/]+)/);
  return {
    base: matches[1],
    gid: matches[2],
    token: matches[3]
  };
}

function getDataUri(buffer, mimeType) {
  buffer = new Buffer(buffer);
  if (!mimeType) {
    mimeType = "text/plain";
  }
  return "data:" + mimeType + ";base64," + buffer.toString("base64");
}

function browserDownload(url, name) {
  var a = document.createElement("a");
  a.href = url;
  if (name) {
    a.download = name;
  }
  a.dispatchEvent(new MouseEvent("click"));
}

function parseSize(value) {
  var matches = value.match(/(\d+) (\S+)/);
  if (matches) {
    value = parseInt(matches[1], 10);
    switch (matches[2]) {
      case "B":
        return value;
      case "KB":
        return value * 1000;
      case "MB":
        return value * 1000000;
      case "GB":
        return value * 1000000000;
    }
  }
  return value;
}

function parseTorrentInfo(xmlDocument) {
  var property = />(.*):<\/span>\s*(.*)$/;
  var torrents = [];
  var forms = xmlDocument.getElementById("torrentinfo").getElementsByTagName("form");
  var i, j, link, fields, matches;
  for (i = 0; i < forms.length - 1; i++) {
    link = forms[i].getElementsByTagName("a")[0];
    torrents[i] = {
      url: link.href,
      name: link.innerText
    };
    fields = forms[i].getElementsByTagName("td");
    for (j = 0; j < fields.length; j++) {
      matches = fields[j].innerHTML.match(property);
      if (matches) {
        torrents[i][matches[1].toLocaleLowerCase()] = matches[2];
      }
    }
  }
  return torrents;
}

function getTorrentsSorted() {
  var key = GM_getValue("sortKey");
  var order = GM_getValue("sortOrder", "desc");
  switch (key) {
    case "size":
      if (order === "desc") {
        return gallery.torrents.sort(function (a, b) {
          return parseSize(b.size) - parseSize(a.size);
        });
      }
      return gallery.torrents.sort(function (a, b) {
        return parseSize(a.size) - parseSize(b.size);
      });
    default:
      key = "downloads";
      //fallthrough
    case "seeds":
    case "peers":
    case "downloads":
      if (order === "desc") {
        return gallery.torrents.sort(function (a, b) {
          return parseInt(b[key], 10) - parseInt(a[key], 10);
        });
      }
      return gallery.torrents.sort(function (a, b) {
        return parseInt(a[key], 10) - parseInt(b[key], 10);
      });
    case "posted":
    case "uploader":
      if (order === "desc") {
        return gallery.torrents.sort(function (a, b) {
          return String(b.posted).localeCompare(a.posted);
        });
      }
      return gallery.torrents.sort(function (a, b) {
        return String(a.posted).localeCompare(b.posted);
      });
  }
}

function updateSortOrder(event) {
  event.preventDefault();
  var key = GM_getValue("sortKey", "downloads");
  var value;
  switch (event.target.textContent) {
    case "DLs":
      value = "downloads";
      break;
    case "Added":
      value = "posted";
      break;
    default:
      value = name.toLowerCase();
  }
  if (value === key) {
    if (GM_getValue("sortOrder") === "asc") {
      GM_setValue("sortOrder", "desc");
    }
    else {
      GM_setValue("sortOrder", "asc");
    }
  }
  else {
    GM_setValue("sortKey", value);
    GM_setValue("sortOrder", "desc");
  }
  var table = document.getElementById("torrentinfo");
  table.parentNode.replaceChild(createTable(), table);
}

function downloadTorrent(event) {
  if (!parseTorrent) {
    return;
  }
  event.preventDefault();
  var url = event.target.href;
  GM_xmlhttpRequest({
    url: url,
    method: "GET",
    overrideMimeType: "application/x-bittorrent; charset=x-user-defined",
    onload: function (response) {
      var data = new Buffer(response.responseText, "binary");
      if (!data.length) {
        browserDownload(url);
        return;
      }
      var torrent = parseTorrent(data);
      torrent.created = gallery.posted;
      torrent.createdBy = gallery.uploader;
      torrent.comment = gallery.url;
      //torrent.name = gallery.name;
      var uri = getDataUri(parseTorrent.toTorrentFile(torrent), "application/x-bittorrent");
      browserDownload(uri, gallery.name + ".torrent");
    },
    onerror: function () {
      browserDownload(url);
    }
  });
}

function insertTorrentTable() {
  if (gallery.torrents.length) {
    var wrapper = document.createElement("div");
    wrapper.className = "gm";
    wrapper.appendChild(createTable());
    var asm = document.getElementById("asm");
    asm.parentNode.insertBefore(wrapper, asm);
  }
}

function createTable() {
  var header = document.createElement("tr");
  header.appendChild(createLabel("Added", 95));
  var elem = document.createElement("th");
  elem.textContent = "Torrent Name";
  header.appendChild(elem);
  header.appendChild(createLabel("Size", 70));
  header.appendChild(createLabel("Seeds", 35));
  header.appendChild(createLabel("Peers", 35));
  header.appendChild(createLabel("DLs", 40));
  header.appendChild(createLabel("Uploader", 90));

  var tbody = document.createElement("tbody");
  tbody.append(header);
  var torrents = getTorrentsSorted();
  for (var i = 0; i < torrents.length; i++) {
    tbody.appendChild(createRow(torrents[i], i % 2));
  }

  var table = document.createElement("table");
  table.id = "torrentinfo";
  table.className = "itg";
  table.style = "border:0; padding:0; margin:10px 8px; min-width:0; max-width:calc(100% - 16px); outline:1px solid black";
  table.appendChild(tbody);
  return table;
}

function createLabel(name, width) {
  var a = document.createElement("a");
  a.textContent = name;
  a.href = "#";
  a.onclick = updateSortOrder;
  var elem = document.createElement("th");
  elem.style = "text-align:center; width:" + width + "px";
  elem.appendChild(a);
  return elem;
}

function createRow(torrent, mod) {
  var row = document.createElement("tr");
  row.className = "gtr" + mod;
  row.appendChild(createField(torrent.posted, "width:102px; white-space:nowrap"));
  var a = document.createElement("a");
  a.href = torrent.url;
  a.textContent = torrent.name;
  a.onclick = downloadTorrent;
  var div = document.createElement("div");
  div.style = "height:15px; min-width:300px; max-width:600px; overflow:hidden";
  div.appendChild(a);
  var td = document.createElement("td");
  td.className = "itd";
  td.appendChild(div);
  row.appendChild(td);
  row.appendChild(createField(torrent.size, "text-align:right; width:70px; white-space:nowrap"));
  row.appendChild(createField(torrent.seeds, "text-align:right; width:30px"));
  row.appendChild(createField(torrent.peers, "text-align:right; width:30px"));
  row.appendChild(createField(torrent.downloads, "text-align:right; width:40px"));
  row.appendChild(createField(torrent.uploader, "width:90px"));
  return row;
}

function createField(name, style) {
  var elem = document.createElement("td");
  elem.className = "itd";
  elem.textContent = name;
  elem.style = style;
  return elem;
}

<|endoftext|>