<commit_before>const Webtorrent = require('webtorrent');
const pump = require('pump');
const mime = require('mime');
const rangeParser = require('range-parser');
const admin = require("firebase-admin");
const firestore = admin.firestore();
const _ = require('lodash');

const webtorrent = new Webtorrent();
const listTorrent = {}

// From https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
function encodeRFC5987 (str) {
    return encodeURIComponent(str)
      // Note that although RFC3986 reserves "!", RFC5987 does not,
      // so we do not need to escape it
      .replace(/['()]/g, escape) // i.e., %27 %28 %29
      .replace(/\*/g, '%2A')
      // The following are not required for percent-encoding per RFC5987,
      // so we can allow for a little better readability over the wire: |`^
      .replace(/%(?:7C|60|5E)/g, unescape)
  }

class Torrent {
    constructor() {
        firestore.collection('session')
            .orderBy('date')
            .limit(1)
            .onSnapshot(snapshot => {
                snapshot.docChanges.forEach(change => {
                  if (change.type === 'added') {
                    this.addNewTorrent(change.doc.id, change.doc.data().torrentUrl);
                  }
                });
            });
    }
    addNewTorrent(sessionId, torrentFile) {
        webtorrent.add(torrentFile, (torrent) => {
            console.log(`Torrent for ${sessionId} is available`);
            listTorrent[sessionId] = _.orderBy(torrent.files, ['length'], ['desc'])[0];
        });
    }

    serveFile(req, res) {
        if (!listTorrent[req.params.id]) {
            return res.sendStatus(404);
        }
        const file = listTorrent[req.params.id];
        res.statusCode = 200
        res.setHeader('Content-Type', mime.lookup(file.name))

        // Support range-requests
        res.setHeader('Accept-Ranges', 'bytes')

        // Set name of file (for "Save Page As..." dialog)
        res.setHeader(
          'Content-Disposition',
          'inline; filename*=UTF-8\'\'' + encodeRFC5987(file.name)
        )

        // Support DLNA streaming
        res.setHeader('transferMode.dlna.org', 'Streaming')
        res.setHeader(
          'contentFeatures.dlna.org',
          'DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000'
        )

        // `rangeParser` returns an array of ranges, or an error code (number) if
        // there was an error parsing the range.
        var range = rangeParser(file.length, req.headers.range || '')

        if (Array.isArray(range)) {
          res.statusCode = 206 // indicates that range-request was understood

          // no support for multi-range request, just use the first range
          range = range[0]

          res.setHeader(
            'Content-Range',
            'bytes ' + range.start + '-' + range.end + '/' + file.length
          )
          res.setHeader('Content-Length', range.end - range.start + 1)
        } else {
          range = null
          res.setHeader('Content-Length', file.length)
        }

        if (req.method === 'HEAD') {
          return res.end()
        }

        pump(file.createReadStream(range), res)
      }
}

module.exports = Torrent;<commit_msg>remove sort and filter<commit_after>const Webtorrent = require('webtorrent');
const pump = require('pump');
const mime = require('mime');
const rangeParser = require('range-parser');
const admin = require("firebase-admin");
const firestore = admin.firestore();
const _ = require('lodash');

const webtorrent = new Webtorrent();
const listTorrent = {}

// From https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
function encodeRFC5987 (str) {
    return encodeURIComponent(str)
      // Note that although RFC3986 reserves "!", RFC5987 does not,
      // so we do not need to escape it
      .replace(/['()]/g, escape) // i.e., %27 %28 %29
      .replace(/\*/g, '%2A')
      // The following are not required for percent-encoding per RFC5987,
      // so we can allow for a little better readability over the wire: |`^
      .replace(/%(?:7C|60|5E)/g, unescape)
  }

class Torrent {
    constructor() {
        firestore.collection('session')
            .onSnapshot(snapshot => {
                snapshot.docChanges.forEach(change => {
                  if (change.type === 'added') {
                    this.addNewTorrent(change.doc.id, change.doc.data().torrentUrl);
                  }
                });
            });
    }
    addNewTorrent(sessionId, torrentFile) {
        webtorrent.add(torrentFile, (torrent) => {
            console.log(`Torrent for ${sessionId} is available`);
            listTorrent[sessionId] = _.orderBy(torrent.files, ['length'], ['desc'])[0];
        });
    }

    serveFile(req, res) {
        if (!listTorrent[req.params.id]) {
            return res.sendStatus(404);
        }
        const file = listTorrent[req.params.id];
        res.statusCode = 200
        res.setHeader('Content-Type', mime.lookup(file.name))

        // Support range-requests
        res.setHeader('Accept-Ranges', 'bytes')

        // Set name of file (for "Save Page As..." dialog)
        res.setHeader(
          'Content-Disposition',
          'inline; filename*=UTF-8\'\'' + encodeRFC5987(file.name)
        )

        // Support DLNA streaming
        res.setHeader('transferMode.dlna.org', 'Streaming')
        res.setHeader(
          'contentFeatures.dlna.org',
          'DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000'
        )

        // `rangeParser` returns an array of ranges, or an error code (number) if
        // there was an error parsing the range.
        var range = rangeParser(file.length, req.headers.range || '')

        if (Array.isArray(range)) {
          res.statusCode = 206 // indicates that range-request was understood

          // no support for multi-range request, just use the first range
          range = range[0]

          res.setHeader(
            'Content-Range',
            'bytes ' + range.start + '-' + range.end + '/' + file.length
          )
          res.setHeader('Content-Length', range.end - range.start + 1)
        } else {
          range = null
          res.setHeader('Content-Length', file.length)
        }

        if (req.method === 'HEAD') {
          return res.end()
        }

        pump(file.createReadStream(range), res)
      }
}

module.exports = Torrent;<|endoftext|>


<commit_before>'use strict';

var User = require('../models/user_model');
var jobQueue = require('../models/jobQueue_model');

module.exports = function(app, jwtauth) {
  app.post('/concierge', jwtauth, function(req, res) {
    console.log('this should be the users id: ' + req.user._id);
    User.findOne({_id: req.user._id}, function(err, user) {
      if (err) {
        console.log('error finding user to add concierge: ' + err);
        return res.status(500).json({message: 'error finding user'});
      }
      if (user === null) {
        console.log('no user found matching that id');
        return res.status(500).json({message: 'no user found matching that id'});
      }
      user.concierge = true;
      user.conciergeAvailable = false;
      user.save(function(err) {
        if (err) return res.status(500).json({message: 'no user found matching that id'});
        console.log('successfully updated user to concierge: ' + user);
        res.status(202).json({concierge: true});
      });
    });
  });
  app.post('/conciergeAvailable', jwtauth, function(req, res) {
    User.findOne({_id: req.user._id}, function(err, user) {
      if (err) {
        console.log('error finding Concierge to make available: ' + err);
        return res.status(500).json({message: 'error finding concierge'});
      }
      if (user === null) {
        console.log('no user found matching that id');
        return res.status(500).json({message: 'no user found matching that id'});
      }
      user.conciergeAvailable = true;
      user.conciergeJobs = [];
      user.save(function(err) {
        if (err) return res.status(500).json({message: 'no user found matching that id'});
        console.log('successfully updated concierge to available: ' + user._id);
        res.status(202).json({conciergeAvailable: true});
      });
    });
  });
  app.post('/conciergeUnavailable', jwtauth, function(req, res) {
    User.findOne({_id: req.user._id}, function(err, user) {
      if (err) {
        console.log('error finding Concierge to make unavailable: ' + err);
        return res.status(500).json({message: 'error finding concierge'});
      }
      if (user === null) {
        console.log('no user found matching that id');
        return res.status(500).json({message: 'no user found matching that id'});
      }
      user.conciergeAvailable = false;
      user.save(function(err) {
        if (err) return res.status(500).json({message: 'no user found matching that id'});
        console.log('successfully updated concierge to unavailable: ' + user._id);
        res.status(202).json({conciergeAvailable: false});
      });
    });
  });
  app.get('/concierge', jwtauth, function(req, res) {
    var jobsArray;
    User.find({_id: req.user._id}, function(err, jobs) {
      if (err) {
        console.log('error finding concierge: ' + err);
        return res.status(500).json({message: 'error finding concierge'});
      }
      if (jobs === null) {
        console.log('no concierge found matching that id');
        return res.status(500).json({message: 'no concierge found matching that id'});
      }
      jobsArray = jobs;
      res.send(JSON.parse(JSON.stringify(jobsArray)));
      // jobQueue.findById({'id_': { $in: ['546c2d6c3fa04e02000f3ca5', '546c2d6c3fa04e02000f3ca6']}}, function(err, docs) {
      //   if (err) return res.status(500).json({message: 'error finding concierge jobs'});
      //   if (jobs === null) return res.status(500).json({message: 'no jobs found for concierge'});
      //   res.status(200).send(docs);
      // });
    });
  });
};
<commit_msg>concierge wip<commit_after>'use strict';

var User = require('../models/user_model');
var jobQueue = require('../models/jobQueue_model');

module.exports = function(app, jwtauth) {
  app.post('/concierge', jwtauth, function(req, res) {
    console.log('this should be the users id: ' + req.user._id);
    User.findOne({_id: req.user._id}, function(err, user) {
      if (err) {
        console.log('error finding user to add concierge: ' + err);
        return res.status(500).json({message: 'error finding user'});
      }
      if (user === null) {
        console.log('no user found matching that id');
        return res.status(500).json({message: 'no user found matching that id'});
      }
      user.concierge = true;
      user.conciergeAvailable = false;
      user.save(function(err) {
        if (err) return res.status(500).json({message: 'no user found matching that id'});
        console.log('successfully updated user to concierge: ' + user);
        res.status(202).json({concierge: true});
      });
    });
  });
  app.post('/conciergeAvailable', jwtauth, function(req, res) {
    User.findOne({_id: req.user._id}, function(err, user) {
      if (err) {
        console.log('error finding Concierge to make available: ' + err);
        return res.status(500).json({message: 'error finding concierge'});
      }
      if (user === null) {
        console.log('no user found matching that id');
        return res.status(500).json({message: 'no user found matching that id'});
      }
      user.conciergeAvailable = true;
      user.conciergeJobs = [];
      user.save(function(err) {
        if (err) return res.status(500).json({message: 'no user found matching that id'});
        console.log('successfully updated concierge to available: ' + user._id);
        res.status(202).json({conciergeAvailable: true});
      });
    });
  });
  app.post('/conciergeUnavailable', jwtauth, function(req, res) {
    User.findOne({_id: req.user._id}, function(err, user) {
      if (err) {
        console.log('error finding Concierge to make unavailable: ' + err);
        return res.status(500).json({message: 'error finding concierge'});
      }
      if (user === null) {
        console.log('no user found matching that id');
        return res.status(500).json({message: 'no user found matching that id'});
      }
      user.conciergeAvailable = false;
      user.save(function(err) {
        if (err) return res.status(500).json({message: 'no user found matching that id'});
        console.log('successfully updated concierge to unavailable: ' + user._id);
        res.status(202).json({conciergeAvailable: false});
      });
    });
  });
  app.get('/concierge', jwtauth, function(req, res) {
    var jobsArray;
    User.find({_id: req.user._id}, function(err, jobs) {
      if (err) {
        console.log('error finding concierge: ' + err);
        return res.status(500).json({message: 'error finding concierge'});
      }
      if (jobs === null) {
        console.log('no concierge found matching that id');
        return res.status(500).json({message: 'no concierge found matching that id'});
      }
      jobsArray = jobs;
      res.send(JSON.parse(JSON.stringify(jobsArray)).conciergeJobs);
      // jobQueue.findById({'id_': { $in: ['546c2d6c3fa04e02000f3ca5', '546c2d6c3fa04e02000f3ca6']}}, function(err, docs) {
      //   if (err) return res.status(500).json({message: 'error finding concierge jobs'});
      //   if (jobs === null) return res.status(500).json({message: 'no jobs found for concierge'});
      //   res.status(200).send(docs);
      // });
    });
  });
};
<|endoftext|>


<commit_before>const base = require( './base.js' );

module.exports = Object.assign( {}, base, {
    list: [
        'absolute-zero',
        'alpine-ace-can',
        'alpine-ace-chn',
        'alpine-ace-fra',
        'alpine-ace-gbr',
        'alpine-ace-ger',
        'alpine-ace-kor',
        'alpine-ace-usa',
        'alpine-ace',
        'arctic-assassin',
        'assault-trooper',
        'beetlejess',
        'black-knight',
        'blue-squire',
        'blue-team-leader',
        'brilliant-striker',
        'brite-bomber',
        'burnout',
        'catstructor',
        'circuit-breaker',
        'codename-e-l-f-',
        'commando',
        'constructor1',
        'constructor2',
        'crackshot',
        'crimson-scout',
        'cuddle-team-leader',
        'dark-voyager',
        'default',
        'desperado',
        'devastator',
        'dragonsarah',
        'elite-agent',
        'energythief',
        'funk-ops',
        'ghoul-trooper',
        'ginger-gunner',
        'grenadier',
        'gunblazer',
        'havoc',
        'hazardkyle',
        'hero-assassin',
        'hero-base',
        'hero-beetlejess',
        'hero-brawler',
        'hero-catstructor',
        'hero-centurion',
        'hero-commando',
        'hero-controller',
        'hero-dim-mak',
        'hero-dragon',
        'hero-dragonsarah',
        'hero-electro-pulse',
        'hero-energythief',
        'hero-enforcer',
        'hero-fleetfoot',
        'hero-gunblazer',
        'hero-hazardkyle',
        'hero-hotfixer',
        'hero-kinetic-guardian',
        'hero-machinist',
        'hero-marinecorpse',
        'hero-master-grenadier',
        'hero-megabase',
        'hero-pathfinder',
        'hero-phase-scout',
        'hero-plasma-sentinel',
        'hero-plasma-shielder',
        'hero-poisoner',
        'hero-power-base',
        'hero-raider',
        'hero-ranger',
        'hero-recon-scout',
        'hero-rescue-trooper',
        'hero-shock-specialist',
        'hero-shock-trooper',
        'hero-shuriken-master',
        'hero-skirmisher',
        'hero-skullshot',
        'hero-special-forces',
        'hero-stonefoot',
        'hero-striker',
        'hero-support-specialist',
        'hero-survivalist',
        'hero-swordmaster',
        'hero-tank',
        'hero-trailblaster',
        'hero-trailblazer',
        'hero-urban-assault',
        'hero-vanguard',
        'hero-warlord',
        'hyperion',
        'jungle-scout',
        'lama1',
        'lamahero-master-grenadier1',
        'llama',
        'llamamaster',
        'love-ranger',
        'machinist',
        'marinecorpse',
        'mastergoat',
        'merry-marauder',
        'mission-specialist',
        'mogul-master-can',
        'mogul-master-chn',
        'mogul-master-fra',
        'mogul-master-gbr',
        'mogul-master-ger',
        'mogul-master-kor',
        'mogul-master-usa',
        'moon-walker',
        'munitions-expert',
        'ninja1',
        'ninja2',
        'nog-ops',
        'outlander4',
        'outlander6png',
        'pathfinder',
        'raider',
        'raptor',
        'recon-scout',
        'recon-specialist',
        'red-knight',
        'red-nosed-raider',
        'renegade-raider',
        'rex',
        'rogue-agent',
        'rust-lord',
        'sash-sergeant',
        'scarlet-defender',
        'scout',
        'shadow-ops',
        'shurikenmasternew',
        'skull-trooper',
        'skullshot',
        'snorkel-ops',
        'soldier2',
        'soldier3',
        'sparkle-specialist',
        'sub-commander',
        'the-reaper',
        'tower-recon-specialist',
        'tracker',
        'trooper',
        'wukong',
        'yuletide-ranger',
    ],
    type: 'author_flair_css_class',
} );
<commit_msg>all work<commit_after>const base = require( './base.js' );

module.exports = Object.assign( {}, base, {
    list: [
        'absolute-zero',
        'alpine-ace-can',
        'alpine-ace-chn',
        'alpine-ace-fra',
        'alpine-ace-gbr',
        'alpine-ace-ger',
        'alpine-ace-kor',
        'alpine-ace-usa',
        'alpine-ace',
        'arctic-assassin',
        'assault-trooper',
        'beetlejess',
        'black-knight',
        'blue-squire',
        'blue-team-leader',
        'brawler',
        'brilliant-striker',
        'brite-bomber',
        'burnout',
        'catstructor',
        'circuit-breaker',
        'codename-e-l-f-',
        'commando',
        'constructor1',
        'constructor2',
        'crackshot',
        'crimson-scout',
        'cuddle-team-leader',
        'dark-voyager',
        'default',
        'desperado',
        'devastator',
        'dragonsarah',
        'elite-agent',
        'energythief',
        'funk-ops',
        'ghoul-trooper',
        'ginger-gunner',
        'grenadier',
        'gunblazer',
        'havoc',
        'hazardkyle',
        'hero-assassin',
        'hero-base',
        'hero-beetlejess',
        'hero-brawler',
        'hero-catstructor',
        'hero-centurion',
        'hero-commando',
        'hero-controller',
        'hero-dim-mak',
        'hero-dragon',
        'hero-dragonsarah',
        'hero-electro-pulse',
        'hero-energythief',
        'hero-enforcer',
        'hero-fleetfoot',
        'hero-gunblazer',
        'hero-hazardkyle',
        'hero-hotfixer',
        'hero-kinetic-guardian',
        'hero-machinist',
        'hero-marinecorpse',
        'hero-master-grenadier',
        'hero-megabase',
        'hero-pathfinder',
        'hero-phase-scout',
        'hero-plasma-sentinel',
        'hero-plasma-shielder',
        'hero-poisoner',
        'hero-power-base',
        'hero-raider',
        'hero-ranger',
        'hero-recon-scout',
        'hero-rescue-trooper',
        'hero-shock-specialist',
        'hero-shock-trooper',
        'hero-shuriken-master',
        'hero-skirmisher',
        'hero-skullshot',
        'hero-special-forces',
        'hero-stonefoot',
        'hero-striker',
        'hero-support-specialist',
        'hero-survivalist',
        'hero-swordmaster',
        'hero-tank',
        'hero-trailblaster',
        'hero-trailblazer',
        'hero-urban-assault',
        'hero-vanguard',
        'hero-warlord',
        'hyperion',
        'jungle-scout',
        'lama1',
        'lamahero-master-grenadier1',
        'llama',
        'llamamaster',
        'love-ranger',
        'machinist',
        'marinecorpse',
        'mastergoat',
        'merry-marauder',
        'mission-specialist',
        'mogul-master-can',
        'mogul-master-chn',
        'mogul-master-fra',
        'mogul-master-gbr',
        'mogul-master-ger',
        'mogul-master-kor',
        'mogul-master-usa',
        'moon-walker',
        'munitions-expert',
        'ninja1',
        'ninja2',
        'nog-ops',
        'outlander4',
        'outlander6png',
        'pathfinder',
        'raider',
        'raptor',
        'recon-scout',
        'recon-specialist',
        'red-knight',
        'red-nosed-raider',
        'renegade-raider',
        'rex',
        'rogue-agent',
        'rust-lord',
        'sash-sergeant',
        'scarlet-defender',
        'scout',
        'shadow-ops',
        'shurikenmasternew',
        'skull-trooper',
        'skullshot',
        'snorkel-ops',
        'soldier2',
        'soldier3',
        'sparkle-specialist',
        'sub-commander',
        'tactics-officer',
        'the-reaper',
        'tower-recon-specialist',
        'tracker',
        'trooper',
        'wukong',
        'yuletide-ranger',
    ],
    type: 'author_flair_css_class',
} );
<|endoftext|>


<commit_before>const _ = require('lodash');
const debug = require('ghost-ignition').debug('users');
const common = require('../lib/common');

module.exports = function ({
    stripe,
    Member
}) {
    async function get(data, options) {
        debug(`get id:${data.id} email:${data.email}`);
        return Member.findOne(data, options);
    }

    async function destroy(data, options) {
        debug(`destroy id:${data.id} email:${data.email}`);
        const member = await Member.findOne(data, options);
        if (!member) {
            return;
        }

        if (stripe && options.cancelStripeSubscriptions) {
            await stripe.cancelAllSubscriptions(member);
        }

        return Member.destroy({
            id: data.id
        }, options);
    }

    async function update(data, options) {
        debug(`update id:${options.id}`);
        return Member.edit(_.pick(data, [
            'email',
            'name',
            'note',
            'labels',
            'geolocation'
        ]), options);
    }

    async function list(options = {}) {
        return Member.findPage(options);
    }

    async function create({email, name, note, labels, geolocation}, options) {
        debug(`create email:${email}`);

        /** Member.add model method expects label object array*/
        if (labels) {
            labels.forEach((label, index) => {
                if (_.isString(label)) {
                    labels[index] = {name: label};
                }
            });
        }

        return Member.add({
            email,
            name,
            note,
            labels,
            geolocation
        }, options);
    }

    function safeStripe(methodName) {
        return async function (...args) {
            if (stripe) {
                return await stripe[methodName](...args);
            }
        };
    }

    async function linkStripeCustomerById(customerId, memberId) {
        if (!stripe) {
            return;
        }
        const member = await get({id: memberId});
        return stripe.linkStripeCustomer(customerId, member);
    }

    async function setComplimentarySubscriptionById(memberId) {
        if (!stripe) {
            return;
        }
        const member = await get({id: memberId});
        return stripe.setComplimentarySubscription(member);
    }

    async function updateSubscription(memberId, {cancelAtPeriodEnd, subscriptionId}) {
        // Don't allow removing subscriptions that don't belong to the member
        const member = await get({id: memberId});
        const subscriptions = await stripe.getSubscriptions(member);
        const subscription = subscriptions.find(sub => sub.id === subscriptionId);
        if (!subscription) {
            throw new common.errors.BadRequestError({
                message: 'Updating subscription failed! Could not find subscription'
            });
        }

        if (cancelAtPeriodEnd === undefined) {
            throw new common.errors.BadRequestError({
                message: 'Updating subscription failed!',
                help: 'Request should contain "cancel" field.'
            });
        }
        const subscriptionUpdate = {
            id: subscription.id,
            cancel_at_period_end: !!(cancelAtPeriodEnd)
        };

        await stripe.updateSubscriptionFromClient(subscriptionUpdate);
    }

    return {
        create,
        update,
        list,
        get,
        destroy,
        updateSubscription,
        setComplimentarySubscription: safeStripe('setComplimentarySubscription'),
        setComplimentarySubscriptionById,
        cancelComplimentarySubscription: safeStripe('cancelComplimentarySubscription'),
        cancelStripeSubscriptions: safeStripe('cancelComplimentarySubscription'),
        getStripeCustomer: safeStripe('getCustomer'),
        createStripeCustomer: safeStripe('createCustomer'),
        createComplimentarySubscription: safeStripe('createComplimentarySubscription'),
        linkStripeCustomer: safeStripe('linkStripeCustomer'),
        linkStripeCustomerById
    };
};
<commit_msg>🐛 Fixed create and update user methods to account for created_at and subscribed fields<commit_after>const _ = require('lodash');
const debug = require('ghost-ignition').debug('users');
const common = require('../lib/common');

module.exports = function ({
    stripe,
    Member
}) {
    async function get(data, options) {
        debug(`get id:${data.id} email:${data.email}`);
        return Member.findOne(data, options);
    }

    async function destroy(data, options) {
        debug(`destroy id:${data.id} email:${data.email}`);
        const member = await Member.findOne(data, options);
        if (!member) {
            return;
        }

        if (stripe && options.cancelStripeSubscriptions) {
            await stripe.cancelAllSubscriptions(member);
        }

        return Member.destroy({
            id: data.id
        }, options);
    }

    async function update(data, options) {
        debug(`update id:${options.id}`);
        return Member.edit(_.pick(data, [
            'email',
            'name',
            'note',
            'subscribed',
            'labels',
            'geolocation'
        ]), options);
    }

    async function list(options = {}) {
        return Member.findPage(options);
    }

    async function create(data, options) {
        const {email, labels} = data;

        debug(`create email:${email}`);

        /** Member.add model method expects label object array*/
        if (labels) {
            labels.forEach((label, index) => {
                if (_.isString(label)) {
                    labels[index] = {name: label};
                }
            });
        }

        return Member.add(Object.assign(
            {},
            {labels},
            _.pick(data, [
                'email',
                'name',
                'note',
                'subscribed',
                'geolocation',
                'created_at'
            ])), options);
    }

    function safeStripe(methodName) {
        return async function (...args) {
            if (stripe) {
                return await stripe[methodName](...args);
            }
        };
    }

    async function linkStripeCustomerById(customerId, memberId) {
        if (!stripe) {
            return;
        }
        const member = await get({id: memberId});
        return stripe.linkStripeCustomer(customerId, member);
    }

    async function setComplimentarySubscriptionById(memberId) {
        if (!stripe) {
            return;
        }
        const member = await get({id: memberId});
        return stripe.setComplimentarySubscription(member);
    }

    async function updateSubscription(memberId, {cancelAtPeriodEnd, subscriptionId}) {
        // Don't allow removing subscriptions that don't belong to the member
        const member = await get({id: memberId});
        const subscriptions = await stripe.getSubscriptions(member);
        const subscription = subscriptions.find(sub => sub.id === subscriptionId);
        if (!subscription) {
            throw new common.errors.BadRequestError({
                message: 'Updating subscription failed! Could not find subscription'
            });
        }

        if (cancelAtPeriodEnd === undefined) {
            throw new common.errors.BadRequestError({
                message: 'Updating subscription failed!',
                help: 'Request should contain "cancel" field.'
            });
        }
        const subscriptionUpdate = {
            id: subscription.id,
            cancel_at_period_end: !!(cancelAtPeriodEnd)
        };

        await stripe.updateSubscriptionFromClient(subscriptionUpdate);
    }

    return {
        create,
        update,
        list,
        get,
        destroy,
        updateSubscription,
        setComplimentarySubscription: safeStripe('setComplimentarySubscription'),
        setComplimentarySubscriptionById,
        cancelComplimentarySubscription: safeStripe('cancelComplimentarySubscription'),
        cancelStripeSubscriptions: safeStripe('cancelComplimentarySubscription'),
        getStripeCustomer: safeStripe('getCustomer'),
        createStripeCustomer: safeStripe('createCustomer'),
        createComplimentarySubscription: safeStripe('createComplimentarySubscription'),
        linkStripeCustomer: safeStripe('linkStripeCustomer'),
        linkStripeCustomerById
    };
};
<|endoftext|>


<commit_before>var through = require('through');
var hyperglue = require('hyperglue');
var domify = require('domify');

module.exports = function (html, opts, cb) {
    if (typeof opts === 'function') {
        cb = opts;
        opts = {};
    }
    if (!opts) opts = {};
    var elements = {};

    var className = classNameOf(html);

    var tr = through(function (line) {
        var row;
        if (isInt8Array(line)) {
            var s = '';
            for (var i = 0; i < line.length; i++) s += line[i];
            line = s;
        }
        if (typeof line === 'string') {
            try { row = JSON.parse(line) }
            catch (err) { this.emit('error', err) }
        }
        else row = line;

        var res = cb.call(this, row);
        if (!res) return;
        var keys = objectKeys(res);
        var streams = [];

        for (var i = 0; i < keys.length; i++) (function (key) {
            var x = res[key];
            if (isStream(x)) {
                delete res[key];
                streams.push([ key, x ]);
            }
            else if (x && typeof x === 'object' && isStream(x._html)) {
                var st = x._html;
                delete x._html;
                streams.push([ key, st ]);
            }
            else if (x && typeof x === 'object' && isStream(x._text)) {
                var st = x._text;
                delete x._text;
                streams.push([ key, st ]);
            }
        })(keys[i]);

        var type, elem;
        if (opts.key && row.key && elements[row.key]) {
            elem = hyperglue(elements[row.key], res);
            type = 'update';
        }
        else {
            elem = hyperglue(html, res);
            type = 'element';
        }
        if (opts.key && row.key) {
            elements[row.key] = elem;
        }

        for (var i = 0; i < streams.length; i++) (function (ks) {
            var key = ks[0], stream = ks[1];
            tr.emit('stream', stream, elem);
            var cur = elem.querySelector(key);
            if (!cur) return;

            stream.on('element', function (elem) {
                cur.appendChild(elem);
                stream.removeListener('data', ondata);
            });
            stream.on('data', ondata);
            function ondata (e) { cur.innerHTML += e }
        })(streams[i]);

        this.emit(type, elem);
        this.queue(elem.outerHTML);
    });

    tr.prependTo = function (t) {
        var target = getTarget(t);

        tr.on('element', function (elem) {
            target.insertBefore(elem, target.childNodes[0]);
        });
        return tr;
    };

    tr.appendTo = function (t) {
        var target = getTarget(t);

        tr.on('element', function (elem) {
            target.appendChild(elem);
        });
        return tr;
    };

    tr.sortTo = function (t, cmp) {
        if (typeof cmp !== 'function') {
            throw new Error('comparison function not provided');
        }
        var target = getElem(t);

        var nodes = [].slice.call(target.getElementsByClassName(className));
        var sorted = nodes.slice().sort(cmp);
        for (var i = 0; i < nodes.length; i++) {
            if (target.childNodes[i] === sorted[i]) continue;
            target.removeChild(sorted[i]);
            target.insertBefore(sorted[i], target.childNodes[i]);
        }

        tr.on('element', onupdate);
        tr.on('update', onupdate);

        getTarget(t, target);
        return tr;

        function onupdate (elem) {
            var nodes = target.getElementsByClassName(className);
            for (var i = 0; i < nodes.length; i++) {
                if (nodes[i] === elem) continue;
                var n = cmp(elem, nodes[i]);
                if (n < 0) {
                    if (hasChild(target, elem)) {
                        target.removeChild(elem);
                    }
                    target.insertBefore(elem, nodes[i]);
                    return;
                }
            }
            target.appendChild(elem);
        }
    };

    var emittedElements = false;
    tr.className = className;
    return tr;

    function getTarget (t, target) {
        if (!target) target = getElem(t);
        tr.emit('parent', target);
        if (!className) return target;
        if (emittedElements) return target;
        emittedElements = true;

        var elems = target.querySelectorAll('.' + className);
        for (var i = 0; i < elems.length; i++) {
            var elem = elems[i];
            var key = opts.key && elem.getAttribute(opts.key);
            if (key) elements[key] = elem;
            tr.emit('element', elem);
        }
        return target;
    }
};

function classNameOf (html) {
    var elems = domify(html);
    if (elems.length) return elems[0].getAttribute('class');
}

function hasChild (node, child) {
    var nodes = node.childNodes;
    for (var i = 0; i < nodes.length; i++) {
        if (nodes[i] === child) return true;
    }
    return false;
}

function getElem (target) {
    if (typeof target === 'string') {
        return document.querySelector(target);
    }
    return target;
}

function isInt8Array (line) {
    return line && typeof line === 'object'
        && line.constructor === 'function'
        && line.constructor.name === 'Int8Array'
    ;
}

function isStream (x) {
    return x && typeof x.pipe === 'function';
}

var objectKeys = Object.keys || function (obj) {
    var keys = [];
    for (var key in obj) keys.push(key);
    return keys;
};
<commit_msg>fix the sorting algorithm for live-updating elements<commit_after>var through = require('through');
var hyperglue = require('hyperglue');
var domify = require('domify');

module.exports = function (html, opts, cb) {
    if (typeof opts === 'function') {
        cb = opts;
        opts = {};
    }
    if (!opts) opts = {};
    var elements = {};

    var className = classNameOf(html);

    var tr = through(function (line) {
        var row;
        if (isInt8Array(line)) {
            var s = '';
            for (var i = 0; i < line.length; i++) s += line[i];
            line = s;
        }
        if (typeof line === 'string') {
            try { row = JSON.parse(line) }
            catch (err) { this.emit('error', err) }
        }
        else row = line;

        var res = cb.call(this, row);
        if (!res) return;
        var keys = objectKeys(res);
        var streams = [];

        for (var i = 0; i < keys.length; i++) (function (key) {
            var x = res[key];
            if (isStream(x)) {
                delete res[key];
                streams.push([ key, x ]);
            }
            else if (x && typeof x === 'object' && isStream(x._html)) {
                var st = x._html;
                delete x._html;
                streams.push([ key, st ]);
            }
            else if (x && typeof x === 'object' && isStream(x._text)) {
                var st = x._text;
                delete x._text;
                streams.push([ key, st ]);
            }
        })(keys[i]);

        var type, elem;
        if (opts.key && row.key && elements[row.key]) {
            elem = hyperglue(elements[row.key], res);
            type = 'update';
        }
        else {
            elem = hyperglue(html, res);
            type = 'element';
        }
        if (opts.key && row.key) {
            elements[row.key] = elem;
        }

        for (var i = 0; i < streams.length; i++) (function (ks) {
            var key = ks[0], stream = ks[1];
            tr.emit('stream', stream, elem);
            var cur = elem.querySelector(key);
            if (!cur) return;

            stream.on('element', function (elem) {
                cur.appendChild(elem);
                stream.removeListener('data', ondata);
            });
            stream.on('data', ondata);
            function ondata (e) { cur.innerHTML += e }
        })(streams[i]);

        this.emit(type, elem);
        this.queue(elem.outerHTML);
    });

    tr.prependTo = function (t) {
        var target = getTarget(t);

        tr.on('element', function (elem) {
            target.insertBefore(elem, target.childNodes[0]);
        });
        return tr;
    };

    tr.appendTo = function (t) {
        var target = getTarget(t);

        tr.on('element', function (elem) {
            target.appendChild(elem);
        });
        return tr;
    };

    tr.sortTo = function (t, cmp) {
        if (typeof cmp !== 'function') {
            throw new Error('comparison function not provided');
        }
        var target = getElem(t);

        var sorted = [].slice.call(target.getElementsByClassName(className));
        sorted.sort(cmp);
        for (var i = 0; i < sorted.length; i++) {
            if (target.childNodes[i] === sorted[i]) continue;
            target.removeChild(sorted[i]);
            target.insertBefore(sorted[i], target.childNodes[i]);
        }

        tr.on('element', onupdate);
        tr.on('update', function (elem) {
            if (hasChild(target, elem)) {
                target.removeChild(elem);
            }
            onupdate(elem);
        });

        getTarget(t, target);
        return tr;

        function onupdate (elem) {
            var nodes = target.getElementsByClassName(className);
            for (var i = 0; i < nodes.length; i++) {
                if (nodes[i] === elem) continue;
                var n = cmp(elem, nodes[i]);
                if (n < 0) {
                    if (hasChild(target, elem)) {
                        target.removeChild(elem);
                    }
                    target.insertBefore(elem, nodes[i]);
                    return;
                }
            }
            target.appendChild(elem);
        }
    };

    var emittedElements = false;
    tr.className = className;
    return tr;

    function getTarget (t, target) {
        if (!target) target = getElem(t);
        tr.emit('parent', target);
        if (!className) return target;
        if (emittedElements) return target;
        emittedElements = true;

        var elems = target.querySelectorAll('.' + className);
        for (var i = 0; i < elems.length; i++) {
            var elem = elems[i];
            var key = opts.key && elem.getAttribute(opts.key);
            if (key) elements[key] = elem;
            tr.emit('element', elem);
        }
        return target;
    }
};

function classNameOf (html) {
    var elems = domify(html);
    if (elems.length) return elems[0].getAttribute('class');
}

function hasChild (node, child) {
    var nodes = node.childNodes;
    for (var i = 0; i < nodes.length; i++) {
        if (nodes[i] === child) return true;
    }
    return false;
}

function getElem (target) {
    if (typeof target === 'string') {
        return document.querySelector(target);
    }
    return target;
}

function isInt8Array (line) {
    return line && typeof line === 'object'
        && line.constructor === 'function'
        && line.constructor.name === 'Int8Array'
    ;
}

function isStream (x) {
    return x && typeof x.pipe === 'function';
}

var objectKeys = Object.keys || function (obj) {
    var keys = [];
    for (var key in obj) keys.push(key);
    return keys;
};
<|endoftext|>


<commit_before>var orbits = document.getElementById('orbits');
var prototypeCircle = document.getElementById('circle1');

prototypeCircle.style.display = "none";

(function () {
  var numSteps = 80;
  var fps = 1000/25;

  function getCircleLocation (circle) {
    return {
      x: parseInt(circle.getAttribute('cx')),
      y: parseInt(circle.getAttribute('cy'))
    };
  }

  function newCircle (circleToClone, cx, cy) {
    var circle = circleToClone.cloneNode(false);
    circle.setAttribute('id', '');
    circle.setAttribute('cx', cx);
    circle.setAttribute('cy', cy);
    circle.style.display = "block";
    orbits.appendChild(circle);
    return circle;
  }

  var Motions = (function() {
    function* diagonal(cPosition, xDirection, yDirection, stepSize) {
      cPosition = yield cPosition;
      var xStep = xDirection * stepSize;
      var yStep = yDirection * stepSize;
      while (1) {
        var next = {
          x: cPosition.value.x + xStep,
          y: cPosition.value.y + yStep
        };
        cPosition = yield next;
      }
    }

    var moveDiagonal = function (circle, xDirection, yDirection, fps) {
      var val;
      var cx = parseInt(circle.getAttribute('cx'));
      var cy = parseInt(circle.getAttribute('cy'));
      var kissIt = diagonal({x: cx, y: cy}, xDirection, yDirection, 3);
      var last = kissIt.next();
      var count = 0;
      var roll = function () {
        last = kissIt.next(last);
        val = last.value;

        cx = val.x;
        cy = val.y;
        this.setAttribute('cx', cx.toString());
        this.setAttribute('cy', cy.toString());

        count += 1;
        if (count > numSteps) {
          clearInterval(rollInterval);
        }
      };
      var rollInterval = setInterval(roll.bind(circle), fps);
    };

    var orbitCircle = function (circle, radius, fps) {
      var rads = 0;
      var cx;
      var cy;
      var sun = {
        x: parseInt(circle.getAttribute('cx')),
        y: parseInt(circle.getAttribute('cy'))
      };
      setInterval(function () {
        rads += .1;
        cx = radius * Math.sin(rads);
        cy = radius * Math.cos(rads);
        cx += sun.x;
        cy += sun.y;
        circle.setAttribute('cx', cx);
        circle.setAttribute('cy', cy);
      }, fps);
    };

    var oscillate = function (circle, start, end, fps) {
      var rads = 0;
      var step = 10;
      var cx;
      var cy;
      setInterval(function () {
        rads += .4;
        cx = parseInt(circle.getAttribute('cx'));
        cy = parseInt(circle.getAttribute('cy'));
        cx += step;
        cy = cy + 30 * Math.sin(rads);

        if (cx + step >= end || cx + step <= start) {
          step *= -1;
        }
        circle.setAttribute('cx', cx);
        circle.setAttribute('cy', cy);
      }, fps);
    };

    var spinOrbit = function (circle, radius, oscillation, fps) {
      var orbitRads = 0;
      var oscillatingRads = 0;
      var c = getCircleLocation(circle);
      var cx;
      var cy;
      setInterval(function () {
        orbitRads += .1;
        oscillatingRads += .5;
        cx = radius * Math.sin(orbitRads);
        cy = radius * Math.cos(orbitRads);
        cx += c.x + oscillation * Math.sin(oscillatingRads);
        cy += c.y + oscillation * Math.cos(oscillatingRads);
        circle.setAttribute('cx', cx);
        circle.setAttribute('cy', cy);
      }, fps);
    };

    var oscillatingOrbit = function (circle, radius, oscillation, fps) {
      var orbitRads = 0;
      var oscillatingRads = 0;
      var c = getCircleLocation(circle);
      var radiusModified;
      var cx;
      var cy;
      setInterval(function () {
        orbitRads += .1;
        oscillatingRads += .8;
        radiusModified = oscillation * Math.sin(oscillatingRads);
        cx = radius * Math.sin(orbitRads);
        cy = radius * Math.cos(orbitRads);
        cx += c.x + radiusModified;
        cy += c.y + radiusModified;
        circle.setAttribute('cx', cx);
        circle.setAttribute('cy', cy);
      }, fps);
    };

    return {
      moveDiagonal: moveDiagonal,
      oscillatingOrbit: oscillatingOrbit,
      oscillate: oscillate,
      spinOrbit: spinOrbit,
      orbitCircle: orbitCircle
    };
  }());

  /** bind the Add Circle Form */
  (function () {
    var form = document.getElementById("addCircle");

    var select = form.querySelector("select[name='direction']");

    // add the motion options
    for (kk in Motions) {
      if (!Motions.hasOwnProperty(kk)) {
        continue;
      }
      var option = document.createElement('option');
      option.setAttribute('name', kk);
      option.text = kk;
      select.appendChild(option);
    }

    var button = form.querySelector('button');
    var cxElm = form.querySelector("input[name='cx']");
    var cyElm = form.querySelector("input[name='cy']");
    var cx, cy;
    var motion;
    button.addEventListener('click', function () {
      cx = cxElm.value;
      cy = cyElm.value;
      motion = select.value;
      var circle = newCircle(prototypeCircle, cx, cy);
      Motions[motion](circle, 1, 1, fps);
    });
  }());
  //
  //var circle2 = newCircle(circle1, 500, 500);
  //var circle3 = newCircle(circle1, 500, 0);
  //var circle4 = newCircle(circle1, 0, 500);
  //var circle5 = newCircle(circle1, 150, 150);
  //var circle6 = newCircle(circle1, 350, 150);
  //var circle7 = newCircle(circle1, 50, 50);
  //var circle8 = newCircle(circle1, 0, 100);
  //var circle9 = newCircle(circle1, 250, 250);
  //var circle10 = newCircle(circle1, 250, 250);
  //
  //moveCircle(circle1, 1, 1, fps);
  //moveCircle(circle2, -1, -1, fps);
  //moveCircle(circle3, -1, 1, fps);
  //moveCircle(circle4, 1, -1, fps);
  //
  //orbitCircle(circle5, 80, fps);
  //orbitCircle(circle6, 80, fps);
  //orbitCircle(circle7, 20, fps);
  //oscillate(circle8, 0, 500);
  //
  //spinOrbit(circle9, 150, 50, fps);
  //
  //oscillatingOrbit(circle10, 200, 50, fps);
}());

<commit_msg>change scope of numSteps<commit_after>var orbits = document.getElementById('orbits');
var prototypeCircle = document.getElementById('circle1');

prototypeCircle.style.display = "none";

(function () {

  var fps = 1000/25;
  function getCircleLocation (circle) {
    return {
      x: parseInt(circle.getAttribute('cx')),
      y: parseInt(circle.getAttribute('cy'))
    };
  }

  function newCircle (circleToClone, cx, cy) {
    var circle = circleToClone.cloneNode(false);
    circle.setAttribute('id', '');
    circle.setAttribute('cx', cx);
    circle.setAttribute('cy', cy);
    circle.style.display = "block";
    orbits.appendChild(circle);
    return circle;
  }

  var Motions = (function() {
    var numSteps = 80;
    function* diagonal(cPosition, xDirection, yDirection, stepSize) {
      cPosition = yield cPosition;
      var xStep = xDirection * stepSize;
      var yStep = yDirection * stepSize;
      while (1) {
        var next = {
          x: cPosition.value.x + xStep,
          y: cPosition.value.y + yStep
        };
        cPosition = yield next;
      }
    }

    var moveDiagonal = function (circle, xDirection, yDirection, fps) {
      var val;
      var cx = parseInt(circle.getAttribute('cx'));
      var cy = parseInt(circle.getAttribute('cy'));
      var kissIt = diagonal({x: cx, y: cy}, xDirection, yDirection, 3);
      var last = kissIt.next();
      var count = 0;
      var roll = function () {
        last = kissIt.next(last);
        val = last.value;

        cx = val.x;
        cy = val.y;
        this.setAttribute('cx', cx.toString());
        this.setAttribute('cy', cy.toString());

        count += 1;
        if (count > numSteps) {
          clearInterval(rollInterval);
        }
      };
      var rollInterval = setInterval(roll.bind(circle), fps);
    };

    var orbitCircle = function (circle, radius, fps) {
      var rads = 0;
      var cx;
      var cy;
      var sun = {
        x: parseInt(circle.getAttribute('cx')),
        y: parseInt(circle.getAttribute('cy'))
      };
      setInterval(function () {
        rads += .1;
        cx = radius * Math.sin(rads);
        cy = radius * Math.cos(rads);
        cx += sun.x;
        cy += sun.y;
        circle.setAttribute('cx', cx);
        circle.setAttribute('cy', cy);
      }, fps);
    };

    var oscillate = function (circle, start, end, fps) {
      var rads = 0;
      var step = 10;
      var cx;
      var cy;
      setInterval(function () {
        rads += .4;
        cx = parseInt(circle.getAttribute('cx'));
        cy = parseInt(circle.getAttribute('cy'));
        cx += step;
        cy = cy + 30 * Math.sin(rads);

        if (cx + step >= end || cx + step <= start) {
          step *= -1;
        }
        circle.setAttribute('cx', cx);
        circle.setAttribute('cy', cy);
      }, fps);
    };

    var spinOrbit = function (circle, radius, oscillation, fps) {
      var orbitRads = 0;
      var oscillatingRads = 0;
      var c = getCircleLocation(circle);
      var cx;
      var cy;
      setInterval(function () {
        orbitRads += .1;
        oscillatingRads += .5;
        cx = radius * Math.sin(orbitRads);
        cy = radius * Math.cos(orbitRads);
        cx += c.x + oscillation * Math.sin(oscillatingRads);
        cy += c.y + oscillation * Math.cos(oscillatingRads);
        circle.setAttribute('cx', cx);
        circle.setAttribute('cy', cy);
      }, fps);
    };

    var oscillatingOrbit = function (circle, radius, oscillation, fps) {
      var orbitRads = 0;
      var oscillatingRads = 0;
      var c = getCircleLocation(circle);
      var radiusModified;
      var cx;
      var cy;
      setInterval(function () {
        orbitRads += .1;
        oscillatingRads += .8;
        radiusModified = oscillation * Math.sin(oscillatingRads);
        cx = radius * Math.sin(orbitRads);
        cy = radius * Math.cos(orbitRads);
        cx += c.x + radiusModified;
        cy += c.y + radiusModified;
        circle.setAttribute('cx', cx);
        circle.setAttribute('cy', cy);
      }, fps);
    };

    return {
      moveDiagonal: moveDiagonal,
      oscillatingOrbit: oscillatingOrbit,
      oscillate: oscillate,
      spinOrbit: spinOrbit,
      orbitCircle: orbitCircle
    };
  }());

  /** bind the Add Circle Form */
  (function () {
    var form = document.getElementById("addCircle");

    var select = form.querySelector("select[name='direction']");

    // add the motion options
    for (kk in Motions) {
      if (!Motions.hasOwnProperty(kk)) {
        continue;
      }
      var option = document.createElement('option');
      option.setAttribute('name', kk);
      option.text = kk;
      select.appendChild(option);
    }

    var button = form.querySelector('button');
    var cxElm = form.querySelector("input[name='cx']");
    var cyElm = form.querySelector("input[name='cy']");
    var cx, cy;
    var motion;
    button.addEventListener('click', function () {
      cx = cxElm.value;
      cy = cyElm.value;
      motion = select.value;
      var circle = newCircle(prototypeCircle, cx, cy);
      Motions[motion](circle, 1, 1, fps);
    });
  }());
  //
  //var circle2 = newCircle(circle1, 500, 500);
  //var circle3 = newCircle(circle1, 500, 0);
  //var circle4 = newCircle(circle1, 0, 500);
  //var circle5 = newCircle(circle1, 150, 150);
  //var circle6 = newCircle(circle1, 350, 150);
  //var circle7 = newCircle(circle1, 50, 50);
  //var circle8 = newCircle(circle1, 0, 100);
  //var circle9 = newCircle(circle1, 250, 250);
  //var circle10 = newCircle(circle1, 250, 250);
  //
  //moveCircle(circle1, 1, 1, fps);
  //moveCircle(circle2, -1, -1, fps);
  //moveCircle(circle3, -1, 1, fps);
  //moveCircle(circle4, 1, -1, fps);
  //
  //orbitCircle(circle5, 80, fps);
  //orbitCircle(circle6, 80, fps);
  //orbitCircle(circle7, 20, fps);
  //oscillate(circle8, 0, 500);
  //
  //spinOrbit(circle9, 150, 50, fps);
  //
  //oscillatingOrbit(circle10, 200, 50, fps);
}());

<|endoftext|>


<commit_before>/**
 * Octahedrons have 8 sides. This octahedron supports subdivision.
 *
 * Vertices have 'smooth' normals,
 * to make a sharp edge choose a material that uses face normals instead.
 *
 * @author daniel.deady@knectar.com
 * @param radius
 * @param detail Final number of triangles = 4^detail * 8
 */

THREE.OctahedronGeometry = function ( radius, detail ) {

	THREE.Geometry.call( this );

	detail = isFinite(detail) ? detail : 3; // allow a zero value

	var that = this; // ugly scope hack

	prepare( new THREE.Vector3( +1, 0, 0 ) ); // right
	prepare( new THREE.Vector3( -1, 0, 0 ) ); // left
	prepare( new THREE.Vector3( 0, +1, 0 ) ); // up
	prepare( new THREE.Vector3( 0, -1, 0 ) ); // down
	prepare( new THREE.Vector3( 0, 0, +1 ) ); // front
	prepare( new THREE.Vector3( 0, 0, -1 ) ); // back
	var midpoints = [], p = this.vertices;

	// careful to output faces counter-clockwise, that is required for meshes
	make( p[0], p[2], p[4], detail );
	make( p[0], p[4], p[3], detail );
	make( p[0], p[3], p[5], detail );
	make( p[0], p[5], p[2], detail );
	make( p[1], p[2], p[5], detail );
	make( p[1], p[5], p[3], detail );
	make( p[1], p[3], p[4], detail );
	make( p[1], p[4], p[2], detail );

	/**
	 * Project vector onto sphere's surface
	 */
	function prepare( vector ) {

		var normal = vector.clone().normalize();
		var vertex = new THREE.Vertex( normal.clone().multiplyScalar( radius ) );
		vertex.index = that.vertices.push( vertex ) - 1;

		// Texture coords are equivalent to map coords, calculate angle and convert to fraction of a circle.
		var u = azimuth( vector ) / 2 / Math.PI + 0.5;
		var v = inclination( vector ) / Math.PI + 0.5;
		vertex.uv = new THREE.UV( u, v );

		return vertex;

	}

	/**
	 * Approximate a curved face with recursively sub-divided triangles.
	 */
	function make( v1, v2, v3, detail ) {

		if ( detail < 1 ) {

			var face = new THREE.Face3( v1.index, v2.index, v3.index, [ v1.position, v2.position, v3.position ] );
			face.centroid.addSelf( v1.position ).addSelf( v2.position ).addSelf( v3.position ).divideScalar( 3 );
			face.normal = face.centroid.clone().normalize();
			that.faces.push( face );

			var azi = azimuth( face.centroid );
			that.faceVertexUvs[ 0 ].push( [
				correctUV( v1.uv, v1.position, azi ),
				correctUV( v2.uv, v2.position, azi ),
				correctUV( v3.uv, v3.position, azi )
			] );

		}
		else {

			detail -= 1;
			// split triangle into 4 smaller triangles
			make( v1, midpoint( v1, v2 ), midpoint( v1, v3 ), detail ); // top quadrant
			make( midpoint( v1, v2 ), v2, midpoint( v2, v3 ), detail ); // left quadrant
			make( midpoint( v1, v3 ), midpoint( v2, v3 ), v3, detail ); // right quadrant
			make( midpoint( v1, v2 ), midpoint( v2, v3 ), midpoint( v1, v3 ), detail ); // center quadrant

		}

	}

	function midpoint( v1, v2 ) {

		if ( !midpoints[ v1.index ] ) midpoints[ v1.index ] = [];
		if ( !midpoints[ v2.index ] ) midpoints[ v2.index ] = [];
		var mid = midpoints[ v1.index ][ v2.index ];
		if ( mid === undefined ) {
			// generate mean point and project to surface with prepare()
			midpoints[ v1.index ][ v2.index ] = midpoints[ v2.index ][ v1.index ] = mid = prepare(
				new THREE.Vector3().add( v1.position, v2.position ).divideScalar( 2 )
			);
		}
		return mid;

	}

	/**
	 * Angle around the Y axis, counter-clockwise when looking from above.
	 */
	function azimuth( vector ) {

		return Math.atan2( vector.z, -vector.x );

	}

	/**
	 * Angle above the XZ plane.
	 */
	function inclination( vector ) {

		return Math.atan2( -vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) );

	}

	/**
	 * Texture fixing helper. Spheres have some odd behaviours.
	 */
	function correctUV( uv, vector, azimuth ) {

		if ( (azimuth < 0) && (uv.u === 1) ) uv = new THREE.UV( uv.u - 1, uv.v );
		if ( (vector.x === 0) && (vector.z === 0) ) uv = new THREE.UV( azimuth / 2 / Math.PI + 0.5, uv.v );
		return uv;

	}

	this.boundingSphere = { radius: radius };

};

THREE.OctahedronGeometry.prototype = new THREE.Geometry();
THREE.OctahedronGeometry.prototype.constructor = THREE.OctahedronGeometry;
<commit_msg>Octahedron subdivisions to default to 0.<commit_after>/**
 * Octahedrons have 8 sides. This octahedron supports subdivision.
 *
 * Vertices have 'smooth' normals,
 * to make a sharp edge choose a material that uses face normals instead.
 *
 * @author daniel.deady@knectar.com
 * @param radius
 * @param detail Final number of triangles = 4^detail * 8
 */

THREE.OctahedronGeometry = function ( radius, detail ) {

	THREE.Geometry.call( this );

	detail = detail || 0;

	var that = this; // ugly scope hack

	prepare( new THREE.Vector3( +1, 0, 0 ) ); // right
	prepare( new THREE.Vector3( -1, 0, 0 ) ); // left
	prepare( new THREE.Vector3( 0, +1, 0 ) ); // up
	prepare( new THREE.Vector3( 0, -1, 0 ) ); // down
	prepare( new THREE.Vector3( 0, 0, +1 ) ); // front
	prepare( new THREE.Vector3( 0, 0, -1 ) ); // back
	var midpoints = [], p = this.vertices;

	// careful to output faces counter-clockwise, that is required for meshes
	make( p[0], p[2], p[4], detail );
	make( p[0], p[4], p[3], detail );
	make( p[0], p[3], p[5], detail );
	make( p[0], p[5], p[2], detail );
	make( p[1], p[2], p[5], detail );
	make( p[1], p[5], p[3], detail );
	make( p[1], p[3], p[4], detail );
	make( p[1], p[4], p[2], detail );

	/**
	 * Project vector onto sphere's surface
	 */
	function prepare( vector ) {

		var normal = vector.clone().normalize();
		var vertex = new THREE.Vertex( normal.clone().multiplyScalar( radius ) );
		vertex.index = that.vertices.push( vertex ) - 1;

		// Texture coords are equivalent to map coords, calculate angle and convert to fraction of a circle.
		var u = azimuth( vector ) / 2 / Math.PI + 0.5;
		var v = inclination( vector ) / Math.PI + 0.5;
		vertex.uv = new THREE.UV( u, v );

		return vertex;

	}

	/**
	 * Approximate a curved face with recursively sub-divided triangles.
	 */
	function make( v1, v2, v3, detail ) {

		if ( detail < 1 ) {

			var face = new THREE.Face3( v1.index, v2.index, v3.index, [ v1.position, v2.position, v3.position ] );
			face.centroid.addSelf( v1.position ).addSelf( v2.position ).addSelf( v3.position ).divideScalar( 3 );
			face.normal = face.centroid.clone().normalize();
			that.faces.push( face );

			var azi = azimuth( face.centroid );
			that.faceVertexUvs[ 0 ].push( [
				correctUV( v1.uv, v1.position, azi ),
				correctUV( v2.uv, v2.position, azi ),
				correctUV( v3.uv, v3.position, azi )
			] );

		}
		else {

			detail -= 1;
			// split triangle into 4 smaller triangles
			make( v1, midpoint( v1, v2 ), midpoint( v1, v3 ), detail ); // top quadrant
			make( midpoint( v1, v2 ), v2, midpoint( v2, v3 ), detail ); // left quadrant
			make( midpoint( v1, v3 ), midpoint( v2, v3 ), v3, detail ); // right quadrant
			make( midpoint( v1, v2 ), midpoint( v2, v3 ), midpoint( v1, v3 ), detail ); // center quadrant

		}

	}

	function midpoint( v1, v2 ) {

		if ( !midpoints[ v1.index ] ) midpoints[ v1.index ] = [];
		if ( !midpoints[ v2.index ] ) midpoints[ v2.index ] = [];
		var mid = midpoints[ v1.index ][ v2.index ];
		if ( mid === undefined ) {
			// generate mean point and project to surface with prepare()
			midpoints[ v1.index ][ v2.index ] = midpoints[ v2.index ][ v1.index ] = mid = prepare(
				new THREE.Vector3().add( v1.position, v2.position ).divideScalar( 2 )
			);
		}
		return mid;

	}

	/**
	 * Angle around the Y axis, counter-clockwise when looking from above.
	 */
	function azimuth( vector ) {

		return Math.atan2( vector.z, -vector.x );

	}

	/**
	 * Angle above the XZ plane.
	 */
	function inclination( vector ) {

		return Math.atan2( -vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) );

	}

	/**
	 * Texture fixing helper. Spheres have some odd behaviours.
	 */
	function correctUV( uv, vector, azimuth ) {

		if ( (azimuth < 0) && (uv.u === 1) ) uv = new THREE.UV( uv.u - 1, uv.v );
		if ( (vector.x === 0) && (vector.z === 0) ) uv = new THREE.UV( azimuth / 2 / Math.PI + 0.5, uv.v );
		return uv;

	}

	this.boundingSphere = { radius: radius };

};

THREE.OctahedronGeometry.prototype = new THREE.Geometry();
THREE.OctahedronGeometry.prototype.constructor = THREE.OctahedronGeometry;
<|endoftext|>


<commit_before>var unmon=require("./lib/unmon.js");
var mds=require("./lib/plug/md.js");

unmon.launch({main:mds.spawnHandler(process.env.PWD+"/md/")});
<commit_msg>updated for title argument<commit_after>var unmon=require("./lib/unmon.js");
var mds=require("./lib/plug/md.js");

unmon.launch({main:mds.spawnHandler(process.env.PWD+"/md/","unmon")});
<|endoftext|>


<commit_before>var _ = require('underscore');
var BaseWidget = require('../base');

function BaseOverviewsDataview(query, queryOptions, BaseDataview, queryRewriter, queryRewriteData, options) {
  this.BaseDataview = BaseDataview;
  this.query = query;
  this.queryOptions = queryOptions;
  this.queryRewriter = queryRewriter;
  this.queryRewriteData = queryRewriteData;
  this.options = options;
  this.baseDataview = new this.BaseDataview(this.query, this.queryOptions);
}

module.exports = BaseOverviewsDataview;

BaseOverviewsDataview.prototype = new BaseWidget();
BaseOverviewsDataview.prototype.constructor = BaseOverviewsDataview;

// TODO: parameterized these settings
var SETTINGS = {
    // use overviews as a default fallback strategy
    defaultOverviews: false,

    // minimum ratio of bounding box size to grid size
    zoomLevelFactor: 100.0
};

// Compute zoom level so that the the resolution grid size of the
// selected overview is smaller (zoomLevelFactor times smaller at least)
// than the bounding box size.
BaseOverviewsDataview.prototype.zoomLevelForBbox = function(bbox) {
    var pxPerTile = 256.0;
    var earthWidth = 360.0;
    // TODO: now we assume overviews are computed for 1-pixel tolerance;
    // should use extended overviews metadata to compute this properly.
    if ( bbox ) {
      var bboxValues = _.map(bbox.split(','), function(v) { return +v; });
      var w = Math.abs(bboxValues[2]-bboxValues[0]);
      var h = Math.abs(bboxValues[3]-bboxValues[1]);
      var maxDim = Math.min(w, h);

      // Find minimum suitable z
      // note that the QueryRewirter will use the minimum level overview
      // of level >= z if it exists, and otherwise the base table
      var z = Math.ceil(-Math.log(maxDim*pxPerTile/earthWidth/SETTINGS.zoomLevelFactor)/Math.log(2.0));
      return Math.max(z, 0);
    }
    return 0;
};

BaseOverviewsDataview.prototype.rewrittenQuery = function(query) {
    var zoom_level = this.zoomLevelForBbox(this.options.bbox);
    return this.queryRewriter.query(query, this.queryRewriteData, { zoom_level: zoom_level });
};

// Default behaviour
BaseOverviewsDataview.prototype.defaultSql = function(psql, filters, override, callback) {
    var query = this.query;
    var dataview = this.baseDataview;
    if ( SETTINGS.defaultOverviews ) {
        query = this.rewrittenQuery(query);
        dataview = new this.BaseDataview(query, this.queryOptions);
    }
    return dataview.sql(psql, filters, override, callback);
};

// default implementation that can be override in derived classes:

BaseOverviewsDataview.prototype.sql = function(psql, filters, override, callback) {
    return this.defaultSql(psql, filters, override, callback);
};

BaseOverviewsDataview.prototype.search = function(psql, userQuery, callback) {
    return this.baseDataview.search(psql, userQuery, callback);
};

BaseOverviewsDataview.prototype.format = function(result) {
    return this.baseDataview.format(result);
};

BaseOverviewsDataview.prototype.getType = function() {
    return this.baseDataview.getType();
};

BaseOverviewsDataview.prototype.toString = function() {
    return this.baseDataview.toString();
};
<commit_msg>Increase the ratio used to select an overview level from a bounding box<commit_after>var _ = require('underscore');
var BaseWidget = require('../base');

function BaseOverviewsDataview(query, queryOptions, BaseDataview, queryRewriter, queryRewriteData, options) {
  this.BaseDataview = BaseDataview;
  this.query = query;
  this.queryOptions = queryOptions;
  this.queryRewriter = queryRewriter;
  this.queryRewriteData = queryRewriteData;
  this.options = options;
  this.baseDataview = new this.BaseDataview(this.query, this.queryOptions);
}

module.exports = BaseOverviewsDataview;

BaseOverviewsDataview.prototype = new BaseWidget();
BaseOverviewsDataview.prototype.constructor = BaseOverviewsDataview;

// TODO: parameterized these settings
var SETTINGS = {
    // use overviews as a default fallback strategy
    defaultOverviews: false,

    // minimum ratio of bounding box size to grid size
    // (this would ideally be based on the viewport size in pixels)
    zoomLevelFactor: 1024.0
};

// Compute zoom level so that the the resolution grid size of the
// selected overview is smaller (zoomLevelFactor times smaller at least)
// than the bounding box size.
BaseOverviewsDataview.prototype.zoomLevelForBbox = function(bbox) {
    var pxPerTile = 256.0;
    var earthWidth = 360.0;
    // TODO: now we assume overviews are computed for 1-pixel tolerance;
    // should use extended overviews metadata to compute this properly.
    if ( bbox ) {
      var bboxValues = _.map(bbox.split(','), function(v) { return +v; });
      var w = Math.abs(bboxValues[2]-bboxValues[0]);
      var h = Math.abs(bboxValues[3]-bboxValues[1]);
      var maxDim = Math.min(w, h);

      // Find minimum suitable z
      // note that the QueryRewirter will use the minimum level overview
      // of level >= z if it exists, and otherwise the base table
      var z = Math.ceil(-Math.log(maxDim*pxPerTile/earthWidth/SETTINGS.zoomLevelFactor)/Math.log(2.0));
      return Math.max(z, 0);
    }
    return 0;
};

BaseOverviewsDataview.prototype.rewrittenQuery = function(query) {
    var zoom_level = this.zoomLevelForBbox(this.options.bbox);
    return this.queryRewriter.query(query, this.queryRewriteData, { zoom_level: zoom_level });
};

// Default behaviour
BaseOverviewsDataview.prototype.defaultSql = function(psql, filters, override, callback) {
    var query = this.query;
    var dataview = this.baseDataview;
    if ( SETTINGS.defaultOverviews ) {
        query = this.rewrittenQuery(query);
        dataview = new this.BaseDataview(query, this.queryOptions);
    }
    return dataview.sql(psql, filters, override, callback);
};

// default implementation that can be override in derived classes:

BaseOverviewsDataview.prototype.sql = function(psql, filters, override, callback) {
    return this.defaultSql(psql, filters, override, callback);
};

BaseOverviewsDataview.prototype.search = function(psql, userQuery, callback) {
    return this.baseDataview.search(psql, userQuery, callback);
};

BaseOverviewsDataview.prototype.format = function(result) {
    return this.baseDataview.format(result);
};

BaseOverviewsDataview.prototype.getType = function() {
    return this.baseDataview.getType();
};

BaseOverviewsDataview.prototype.toString = function() {
    return this.baseDataview.toString();
};
<|endoftext|>


<commit_before>// ==UserScript==
// @name          GitHub Indent Comment Blocks
// @version       1.0.0
// @description   A userscript that allows you to indent & outdent blocks in the comment editor
// @license       https://creativecommons.org/licenses/by-sa/4.0/
// @namespace     http://github.com/Mottie
// @include       https://github.com/*
// @include       https://gist.github.com/*
// @run-at        document-idle
// @grant         GM_addStyle
// @grant         GM_getValue
// @grant         GM_setValue
// @grant         GM_registerMenuCommand
// @connect       github.com
// @author        Rob Garrison
// @updateURL     https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-indent-comments.user.js
// @downloadURL   https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-indent-comments.user.js
// ==/UserScript==
/* global GM_addStyle, GM_getValue, GM_setValue, GM_registerMenuCommand */
/* jshint unused:true, esnext:true */
(function() {
	"use strict";

	let timer,
		busy = false,

		spaceSize = GM_getValue('space-size', 2);

	const icons = {
		indent: `
			<svg class="octicon" xmlns="http://www.w3.org/2000/svg" width="12" height="16" viewBox="0 0 12 16">
				<path d="M12 13c0 .6 0 1-.9 1H.9c-.9 0-.9-.4-.9-1s0-1 .9-1h10.2c.88 0 .88.4.88 1zM.92 4h10.2C12 4 12 3.6 12 3s0-1-.9-1H.92c-.9 0-.9.4-.9 1s0 1 .9 1zM11.5 7h-5C6 7 6 7.4 6 8s0 1 .5 1h5c.5 0 .5-.4.5-1s0-1-.5-1zm-7 1L0 5v6z"/>
			</svg>`,
		outdent: `
			<svg class="octicon" xmlns="http://www.w3.org/2000/svg" width="12" height="16" viewBox="0 0 12 16">
				<path d="M12 13c0 .6 0 1-.9 1H.9c-.9 0-.9-.4-.9-1s0-1 .9-1h10.2c.88 0 .88.4.88 1zM.92 4h10.2C12 4 12 3.6 12 3s0-1-.9-1H.92c-.9 0-.9.4-.9 1s0 1 .9 1zm10.7 3H6.4c-.46 0-.4.4-.4 1s-.06 1 .4 1h5.2c.47 0 .4-.4.4-1s.07-1-.4-1zM0 8l4.5-3v6z"/>
			</svg>`
	};

	GM_addStyle(".ghio-in-outdent * { pointer-events:none; }");

	// Add indent & outdent buttons
	function addButtons() {
		busy = true;
		createButton("Outdent");
		createButton("Indent");
		busy = false;
	}

	function createButton(name) {
		const toolbars = $$(".toolbar-commenting"),
			nam = name.toLowerCase(),
			button = document.createElement("button");
		let el,
			indx = toolbars.length;
		if (indx) {
			button.type = "button";
			button.className = `ghio-${nam.toLowerCase()} ghio-in-outdent toolbar-item tooltipped tooltipped-n`;
			button.setAttribute("aria-label", `${name} Selected Text`);
			button.setAttribute("tabindex", "-1");
			button.innerHTML = icons[nam.toLowerCase()];
			while (indx--) {
				el = toolbars[indx];
				if (!$(`.ghio-${nam.toLowerCase()}`, el)) {
					el.insertBefore(button.cloneNode(true), el.childNodes[0]);
				}
			}
		}
	}

	function indent(text) {
		let result = [],
			block = new Array(parseInt(spaceSize, 10) + 1).join(" ");
		(text || "").split(/\r*\n/).forEach(line => {
			result.push(block + line);
		});
		return result.join("\n");
	}

	function outdent(text) {
		let regex = new RegExp(`^\\s{0,${spaceSize}}`),
			result = [];
		(text || "").split(/\r*\n/).forEach(line => {
			result.push(line.replace(regex, ""));
		});
		return result.join("\n");
	}

	function addBindings() {
		$("body").addEventListener("click", function(event) {
			let textarea,
				target = event.target;
			if (target && target.classList.contains("ghio-in-outdent")) {
				textarea = closest(".previewable-comment-form", target);
				textarea = $(".comment-form-textarea", textarea);
				textarea.focus();
				setTimeout(() => {
					surroundSelectedText(
						textarea,
						target.classList.contains("ghio-indent") ? "indent" : "outdent"
					);
				}, 100);
				return false;
			}
		});
		// Add Tab & Shift + Tab
		$("body").addEventListener("keydown", function(event) {
			if (event.key === "Tab") {
				let target = event.target;
				if (target && target.classList.contains("comment-form-textarea")) {
					event.preventDefault();
					target.focus();
					setTimeout(() => {
						surroundSelectedText(
							target,
							// shift + tab = outdent
							event.getModifierState("Shift") ? "outdent" : "indent"
						);
					}, 100);
				}
			}
		});
	}

	function $(selector, el) {
		return (el || document).querySelector(selector);
	}
	function $$(selector, el) {
		return Array.from((el || document).querySelectorAll(selector));
	}
	function closest(selector, el) {
		while (el && el.nodeType === 1) {
			if (el.matches(selector)) {
				return el;
			}
			el = el.parentNode;
		}
		return null;
	}

	// Add GM options
	GM_registerMenuCommand(
		"Indent or outdent size",
		() => {
			const spaces = GM_getValue("indentOutdentSize", "2");
			let val = prompt("Enter number of spaces to indent or outdent:", spaces);
			if (val !== null && typeof val === "string") {
				val =
				spaceSize = val;
				GM_setValue('space-size', val);
			}
		}
	);

	$$("#js-repo-pjax-container, #js-pjax-container, .js-preview-body").forEach(target => {
		new MutationObserver(mutations => {
			mutations.forEach(mutation => {
				let mtarget = mutation.target;
				// preform checks before adding code wrap to minimize function calls
				// update after comments are edited
				if (mtarget === target || mtarget.matches(".js-comment-body, .js-preview-body")) {
					clearTimeout(timer);
					setTimeout(() => {
						if (!busy) {
							addButtons();
						}
					}, 100);
				}
			});
		}).observe(target, {
			childList: true,
			subtree: true
		});
	});

	addBindings();
	addButtons();

	/*eslint-disable */
	/* HEAVILY MODIFIED from https://github.com/timdown/rangyinputs
	 code was unwrapped & unneeded code was removed
	*/
	/**
	* @license Rangy Inputs, a jQuery plug-in for selection and caret manipulation within textareas and text inputs.
	*
	* https://github.com/timdown/rangyinputs
	*
	* For range and selection features for contenteditable, see Rangy.
	* http://code.google.com/p/rangy/
	*
	* Depends on jQuery 1.0 or later.
	*
	* Copyright 2014, Tim Down
	* Licensed under the MIT license.
	* Version: 1.2.0
	* Build date: 30 November 2014
	*/
	var UNDEF = "undefined";
	var getSelection, setSelection, surroundSelectedText;

	// Trio of isHost* functions taken from Peter Michaux's article:
	// http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
	function isHostMethod(object, property) {
		var t = typeof object[property];
		return t === "function" || (!!(t == "object" && object[property])) || t == "unknown";
	}
	function isHostProperty(object, property) {
		return typeof(object[property]) != UNDEF;
	}
	function isHostObject(object, property) {
		return !!(typeof(object[property]) == "object" && object[property]);
	}
	function fail(reason) {
		if (window.console && window.console.log) {
			window.console.log("RangyInputs not supported in your browser. Reason: " + reason);
		}
	}

	function adjustOffsets(el, start, end) {
		if (start < 0) {
			start += el.value.length;
		}
		if (typeof end == UNDEF) {
			end = start;
		}
		if (end < 0) {
			end += el.value.length;
		}
		return { start: start, end: end };
	}

	function makeSelection(el, start, end) {
		return {
			start: start,
			end: end,
			length: end - start,
			text: el.value.slice(start, end)
		};
	}

	function getBody() {
		return isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0];
	}

	var testTextArea = document.createElement("textarea");
	getBody().appendChild(testTextArea);

	if (isHostProperty(testTextArea, "selectionStart") && isHostProperty(testTextArea, "selectionEnd")) {
		getSelection = function(el) {
			var start = el.selectionStart, end = el.selectionEnd;
			return makeSelection(el, start, end);
		};

		setSelection = function(el, startOffset, endOffset) {
			var offsets = adjustOffsets(el, startOffset, endOffset);
			el.selectionStart = offsets.start;
			el.selectionEnd = offsets.end;
		};
	} else if (isHostMethod(testTextArea, "createTextRange") && isHostObject(document, "selection") &&
		isHostMethod(document.selection, "createRange")) {

		getSelection = function(el) {
			var start = 0, end = 0, normalizedValue, textInputRange, len, endRange;
			var range = document.selection.createRange();

			if (range && range.parentElement() == el) {
				len = el.value.length;

				normalizedValue = el.value.replace(/\r\n/g, "\n");
				textInputRange = el.createTextRange();
				textInputRange.moveToBookmark(range.getBookmark());
				endRange = el.createTextRange();
				endRange.collapse(false);
				if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
					start = end = len;
				} else {
					start = -textInputRange.moveStart("character", -len);
					start += normalizedValue.slice(0, start).split("\n").length - 1;
					if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
						end = len;
					} else {
						end = -textInputRange.moveEnd("character", -len);
						end += normalizedValue.slice(0, end).split("\n").length - 1;
					}
				}
			}

			return makeSelection(el, start, end);
		};

		// Moving across a line break only counts as moving one character in a TextRange, whereas a line break in
		// the textarea value is two characters. This function corrects for that by converting a text offset into a
		// range character offset by subtracting one character for every line break in the textarea prior to the
		// offset
		var offsetToRangeCharacterMove = function(el, offset) {
			return offset - (el.value.slice(0, offset).split("\r\n").length - 1);
		};

		setSelection = function(el, startOffset, endOffset) {
			var offsets = adjustOffsets(el, startOffset, endOffset);
			var range = el.createTextRange();
			var startCharMove = offsetToRangeCharacterMove(el, offsets.start);
			range.collapse(true);
			if (offsets.start == offsets.end) {
				range.move("character", startCharMove);
			} else {
				range.moveEnd("character", offsetToRangeCharacterMove(el, offsets.end));
				range.moveStart("character", startCharMove);
			}
			range.select();
		};
	} else {
		getBody().removeChild(testTextArea);
		fail("No means of finding text input caret position");
		return;
	}
	// Clean up
	getBody().removeChild(testTextArea);

	function getValueAfterPaste(el, text) {
		var val = el.value, sel = getSelection(el), selStart = sel.start;
		return {
			value: val.slice(0, selStart) + text + val.slice(sel.end),
			index: selStart,
			replaced: sel.text
		};
	}

	function pasteTextWithCommand(el, text) {
		el.focus();
		var sel = getSelection(el);

		// Hack to work around incorrect delete command when deleting the last word on a line
		setSelection(el, sel.start, sel.end);
		if (text === "") {
			document.execCommand("delete", false, null);
		} else {
			document.execCommand("insertText", false, text);
		}

		return {
			replaced: sel.text,
			index: sel.start
		};
	}

	function pasteTextWithValueChange(el, text) {
		el.focus();
		var valueAfterPaste = getValueAfterPaste(el, text);
		el.value = valueAfterPaste.value;
		return valueAfterPaste;
	}

	var pasteText = function(el, text) {
		var valueAfterPaste = getValueAfterPaste(el, text);
		try {
			var pasteInfo = pasteTextWithCommand(el, text);
			if (el.value == valueAfterPaste.value) {
				pasteText = pasteTextWithCommand;
				return pasteInfo;
			}
		} catch (ex) {
			// Do nothing and fall back to changing the value manually
		}
		pasteText = pasteTextWithValueChange;
		el.value = valueAfterPaste.value;
		return valueAfterPaste;
	};

	var updateSelectionAfterInsert = function(el, startIndex, text, selectionBehaviour) {
		var endIndex = startIndex + text.length;

		selectionBehaviour = (typeof selectionBehaviour == "string") ?
		selectionBehaviour.toLowerCase() : "";

		if ((selectionBehaviour == "collapsetoend" || selectionBehaviour == "select") && /[\r\n]/.test(text)) {
			// Find the length of the actual text inserted, which could vary
			// depending on how the browser deals with line breaks
			var normalizedText = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
			endIndex = startIndex + normalizedText.length;
			var firstLineBreakIndex = startIndex + normalizedText.indexOf("\n");

			if (el.value.slice(firstLineBreakIndex, firstLineBreakIndex + 2) == "\r\n") {
				// Browser uses \r\n, so we need to account for extra \r characters
				endIndex += normalizedText.match(/\n/g).length;
			}
		}

		switch (selectionBehaviour) {
			case "collapsetostart":
				setSelection(el, startIndex, startIndex);
				break;
			case "collapsetoend":
				setSelection(el, endIndex, endIndex);
				break;
			case "select":
				setSelection(el, startIndex, endIndex);
				break;
		}
	};

	surroundSelectedText = function(el, type, selectionBehaviour) {
		let sel = getSelection(el),
			result = type === "indent" ? indent(sel.text) : outdent(sel.text),
			pasteInfo = pasteText(el, result);
		updateSelectionAfterInsert(el, pasteInfo.index, result, selectionBehaviour || "select");
	};
	/*eslint-enable */

})();
<commit_msg>Indent-comments: Use pjax & move caret code<commit_after>// ==UserScript==
// @name          GitHub Indent Comment Blocks
// @version       1.0.1
// @description   A userscript that allows you to indent & outdent blocks in the comment editor
// @license       https://creativecommons.org/licenses/by-sa/4.0/
// @namespace     https://github.com/Mottie
// @include       https://github.com/*
// @include       https://gist.github.com/*
// @require       https://greasyfork.org/scripts/28239-rangy-inputs-mod-js/code/rangy-inputs-modjs.js?version=181769
// @run-at        document-idle
// @grant         GM_addStyle
// @grant         GM_getValue
// @grant         GM_setValue
// @grant         GM_registerMenuCommand
// @connect       github.com
// @author        Rob Garrison
// @updateURL     https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-indent-comments.user.js
// @downloadURL   https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-indent-comments.user.js
// ==/UserScript==
(() => {
	"use strict";

	let spaceSize = GM_getValue("space-size", 2);

	const icons = {
		indent: `
			<svg class="octicon" xmlns="http://www.w3.org/2000/svg" width="12" height="16" viewBox="0 0 12 16">
				<path d="M12 13c0 .6 0 1-.9 1H.9c-.9 0-.9-.4-.9-1s0-1 .9-1h10.2c.88 0 .88.4.88 1zM.92 4h10.2C12 4 12 3.6 12 3s0-1-.9-1H.92c-.9 0-.9.4-.9 1s0 1 .9 1zM11.5 7h-5C6 7 6 7.4 6 8s0 1 .5 1h5c.5 0 .5-.4.5-1s0-1-.5-1zm-7 1L0 5v6z"/>
			</svg>`,
		outdent: `
			<svg class="octicon" xmlns="http://www.w3.org/2000/svg" width="12" height="16" viewBox="0 0 12 16">
				<path d="M12 13c0 .6 0 1-.9 1H.9c-.9 0-.9-.4-.9-1s0-1 .9-1h10.2c.88 0 .88.4.88 1zM.92 4h10.2C12 4 12 3.6 12 3s0-1-.9-1H.92c-.9 0-.9.4-.9 1s0 1 .9 1zm10.7 3H6.4c-.46 0-.4.4-.4 1s-.06 1 .4 1h5.2c.47 0 .4-.4.4-1s.07-1-.4-1zM0 8l4.5-3v6z"/>
			</svg>`
	};

	GM_addStyle(".ghio-in-outdent * { pointer-events:none; }");

	// Add indent & outdent buttons
	function addButtons() {
		createButton("Outdent");
		createButton("Indent");
	}

	function createButton(name) {
		const toolbars = $$(".toolbar-commenting"),
			nam = name.toLowerCase(),
			button = document.createElement("button");
		let el,
			indx = toolbars.length;
		if (indx) {
			button.type = "button";
			button.className = `ghio-${nam.toLowerCase()} ghio-in-outdent toolbar-item tooltipped tooltipped-n`;
			button.setAttribute("aria-label", `${name} Selected Text`);
			button.setAttribute("tabindex", "-1");
			button.innerHTML = icons[nam.toLowerCase()];
			while (indx--) {
				el = toolbars[indx];
				if (!$(`.ghio-${nam.toLowerCase()}`, el)) {
					el.insertBefore(button.cloneNode(true), el.childNodes[0]);
				}
			}
		}
	}

	function indent(text) {
		let result = [],
			block = new Array(parseInt(spaceSize, 10) + 1).join(" ");
		(text || "").split(/\r*\n/).forEach(line => {
			result.push(block + line);
		});
		return result.join("\n");
	}

	function outdent(text) {
		let regex = new RegExp(`^\\s{0,${spaceSize}}`),
			result = [];
		(text || "").split(/\r*\n/).forEach(line => {
			result.push(line.replace(regex, ""));
		});
		return result.join("\n");
	}

	function addBindings() {
		window.rangyInput.init();
		$("body").addEventListener("click", event => {
			let textarea,
				target = event.target;
			if (target && target.classList.contains("ghio-in-outdent")) {
				textarea = closest(".previewable-comment-form", target);
				textarea = $(".comment-form-textarea", textarea);
				textarea.focus();
				setTimeout(() => {
					window.rangyInput.indentSelectedText(
						textarea,
						target.classList.contains("ghio-indent") ? indent : outdent
					);
				}, 100);
				return false;
			}
		});
		// Add Tab & Shift + Tab
		$("body").addEventListener("keydown", event => {
			if (event.key === "Tab") {
				let target = event.target;
				if (target && target.classList.contains("comment-form-textarea")) {
					event.preventDefault();
					target.focus();
					setTimeout(() => {
						window.rangyInput.indentSelectedText(
							target,
							// shift + tab = outdent
							event.getModifierState("Shift") ? outdent : indent
						);
					}, 100);
				}
			}
		});
	}

	function $(selector, el) {
		return (el || document).querySelector(selector);
	}

	function $$(selector, el) {
		return Array.from((el || document).querySelectorAll(selector));
	}

	function closest(selector, el) {
		while (el && el.nodeType === 1) {
			if (el.matches(selector)) {
				return el;
			}
			el = el.parentNode;
		}
		return null;
	}

	// Add GM options
	GM_registerMenuCommand(
		"Indent or outdent size",
		() => {
			const spaces = GM_getValue("indentOutdentSize", "2");
			let val = prompt("Enter number of spaces to indent or outdent:", spaces);
			if (val !== null && typeof val === "string") {
				spaceSize = val;
				GM_setValue("space-size", val);
			}
		}
	);

	document.addEventListener("pjax:end", addButtons);
	addBindings();
	addButtons();
})();
<|endoftext|>