<commit_before>import { Router } from 'express';
import path from 'path';
import passport from 'passport';
import { ensureAuth, backToUrl } from '../utils/helpers';
import { Routes } from 'Routes';
import Groups from 'models/groupModel';

let app = Router();

const BASE_GROUP = process.env.BASE_GROUP;
const NODE_ENV = process.env.NODE_ENV;

// Shibboleth Routes
app.get(
  Routes.Login,
  function(req, res, next) {
    req.session.authRedirectUrl = req.query.returnUrl ? req.query.returnUrl : req.session.authRedirectUrl;
    next();
  },
  passport.authenticate('saml', { failureRedirect: Routes.Welcome, failureFlash: true })
);

app.post(
  '/login/callback',
  passport.authenticate('saml', { failureRedirect: Routes.Welcome, failureFlash: true }),
  async (req, res, next) => {
    // admins are the effective members of the base group
    let admins = (await Groups.GetEffectiveMembers(BASE_GROUP.slice(0, -1))).Payload;

    if (admins && admins.indexOf(req.user.UWNetID) > -1) {
      next();
    } else {
      console.log(`User ${req.user.UWNetID} is not in the admins group, redirect to homepage and log out`);
      req.logout();
      req.session.destroy();
      res.clearCookie('connect.sid', { path: Routes.Welcome });
      res.redirect(Routes.NotAuthorized);
    }
  },
  backToUrl()
);

if (NODE_ENV === 'development') {
  const webpack = require('webpack');
  const webpackDevMiddleware = require('webpack-dev-middleware');
  const webpackHotMiddleware = require('webpack-hot-middleware');
  const webpackConfig = require('../../../webpack.dev.config')(process.env);
  let compiler = webpack(webpackConfig);

  app.use(
    webpackDevMiddleware(compiler, {
      publicPath: webpackConfig.output.publicPath,
      stats: { colors: true },
      watchOptions: {
        ignored: [path.resolve(__dirname, '..', 'config')]
      }
    })
  );
  app.use(
    webpackHotMiddleware(compiler, {
      log: console.log,
      reload: true
    })
  );
  // If you run into a weird "No such file or directory" error here, likely an import failed somewhere
  app.get('*', ensureAuth(), (req, res, next) => {
    var filename = path.join(compiler.outputPath, 'index.html');
    compiler.outputFileSystem.readFile(filename, function(err, result) {
      if (err) {
        return next(err);
      }
      res.set('content-type', 'text/html');
      res.send(result);
      res.end();
    });
  });
}

if (process.env.NODE_ENV === 'production') {
  app.get([...Routes], (req, res) => {
    res.sendFile(path.resolve(__dirname, '..', '..', 'index.html'));
  });
}

export default app;
<commit_msg>logging for admins<commit_after>import { Router } from 'express';
import path from 'path';
import passport from 'passport';
import { ensureAuth, backToUrl } from '../utils/helpers';
import { Routes } from 'Routes';
import Groups from 'models/groupModel';

let app = Router();

const BASE_GROUP = process.env.BASE_GROUP;
const NODE_ENV = process.env.NODE_ENV;

// Shibboleth Routes
app.get(
  Routes.Login,
  function(req, res, next) {
    req.session.authRedirectUrl = req.query.returnUrl ? req.query.returnUrl : req.session.authRedirectUrl;
    next();
  },
  passport.authenticate('saml', { failureRedirect: Routes.Welcome, failureFlash: true })
);

app.post(
  '/login/callback',
  passport.authenticate('saml', { failureRedirect: Routes.Welcome, failureFlash: true }),
  async (req, res, next) => {
    // admins are the effective members of the base group
    let admins = (await Groups.GetEffectiveMembers(BASE_GROUP.slice(0, -1))).Payload;
    console.log('EFFECTIVE ADMINS', admins);
    console.log('LOGGED IN USER', req.user.UWNetID);

    if (admins && admins.indexOf(req.user.UWNetID) > -1) {
      next();
    } else {
      console.log(`User ${req.user.UWNetID} is not in the admins group, redirect to homepage and log out`);
      req.logout();
      req.session.destroy();
      res.clearCookie('connect.sid', { path: Routes.Welcome });
      res.redirect(Routes.NotAuthorized);
    }
  },
  backToUrl()
);

if (NODE_ENV === 'development') {
  const webpack = require('webpack');
  const webpackDevMiddleware = require('webpack-dev-middleware');
  const webpackHotMiddleware = require('webpack-hot-middleware');
  const webpackConfig = require('../../../webpack.dev.config')(process.env);
  let compiler = webpack(webpackConfig);

  app.use(
    webpackDevMiddleware(compiler, {
      publicPath: webpackConfig.output.publicPath,
      stats: { colors: true },
      watchOptions: {
        ignored: [path.resolve(__dirname, '..', 'config')]
      }
    })
  );
  app.use(
    webpackHotMiddleware(compiler, {
      log: console.log,
      reload: true
    })
  );
  // If you run into a weird "No such file or directory" error here, likely an import failed somewhere
  app.get('*', ensureAuth(), (req, res, next) => {
    var filename = path.join(compiler.outputPath, 'index.html');
    compiler.outputFileSystem.readFile(filename, function(err, result) {
      if (err) {
        return next(err);
      }
      res.set('content-type', 'text/html');
      res.send(result);
      res.end();
    });
  });
}

if (process.env.NODE_ENV === 'production') {
  app.get([...Routes], (req, res) => {
    res.sendFile(path.resolve(__dirname, '..', '..', 'index.html'));
  });
}

export default app;
<|endoftext|>


<commit_before>export { default } from 'ember-json-serializer-meta/serializers/application';
<commit_msg>refactor: Remove app/serializer, we do not want it do be default<commit_after><|endoftext|>


<commit_before>'use strict';

import core from 'bower:metal/src/core';

/**
 * Acts as a bridge between Metal.js and jQuery, allowing Metal.js components to
 * be used as jQuery plugins.
 * @type {Object}
 */
var JQueryAdapter = {
	/**
	 * Registers a Metal.js component as a jQuery plugin with the given name.
	 * @param {string} name The name of the plugin that should be registered.
	 * @param {!Function(Object)} Ctor The constructor of the Metal.js component.
	 */
	register(name, Ctor) {
		if (!$) {
			throw new Error('jQuery needs to be included in the page for JQueryAdapter to work.');
		}
		if (!core.isString(name)) {
			throw new Error('The name string is required for registering a plugin');
		}
		if (!core.isFunction(Ctor)) {
			throw new Error('The constructor function is required for registering a plugin');
		}

		$.fn[name] = function(configOrMethodName) {
			var args = Array.prototype.slice.call(arguments, 1);
			return handlePluginCall(name, Ctor, this, configOrMethodName, args);
		};
	}
};

/**
 * Calls a method on the plugin instance for the given element.
 * @param {string} name The name of the plugin.
 * @param {!jQuery} element A jQuery collection with a single element.
 * @param {[type]} methodName [description]
 * @return {*} The return value of the called method.
 */
function callMethod(name, element, methodName, args) {
	var fullName = getPluginFullName(name);
	var instance = element.data(fullName);
	if (!instance) {
		throw new Error('Tried to call method ' + methodName + ' on ' + name + ' plugin' +
			'without initialing it first.');
	}
	if (!isValidMethod(instance, methodName)) {
		throw new Error('Plugin ' + name + ' has no method called ' + methodName);
	}
	return instance[methodName].apply(instance, args);
}

/**
 * Creates an instace of a component for the given element, or updates it if one
 * already exists.
 * @param {string} name The name of the plugin.
 * @param {!Function(Object)} Ctor The constructor of the Metal.js component.
 * @param {!jQuery} element A jQuery collection with a single element.
 * @param {Object} config A config object to be passed to the component instance.
 */
function createOrUpdateInstance(name, Ctor, element, config) {
	var fullName = getPluginFullName(name);
	var instance = element.data(fullName);
	config = $.extend({}, config, {
		element: element[0]
	});
	if (instance) {
		instance.setAttrs(config);
	} else {
		instance = new Ctor(config).render();
		instance.on('*', onMetalEvent.bind(null, name, element));
		element.data(fullName, instance);
	}
}

/**
 * Gets the full name of the given plugin, by appending a prefix to it.
 * @param {string} name The name of the plugin.
 * @return {string}
 */
function getPluginFullName(name) {
	return 'metal-' + name;
}

/**
 * Handles calls to a registered plugin.
 * @param {string} name The name of the plugin.
 * @param {!Function(Object)} Ctor The constructor of the Metal.js component.
 * @param {!jQuery} collection A jQuery collection of elements to handle the plugin for.
 * @param {?(string|Object)} configOrMethodName If this is a string, a method with
 * that name will be called on the appropriate component instance. Otherwise, an
 * the instance (which will be created if it doesn't yet exist) will receive this
 * as its config object.
 * @param {Array} args All other arguments that were passed to the plugin call.
 */
function handlePluginCall(name, Ctor, collection, configOrMethodName, args) {
	if (core.isString(configOrMethodName)) {
		return callMethod(name, $(collection[0]), configOrMethodName, args);
	} else {
		collection.each(function() {
			createOrUpdateInstance(name, Ctor, $(this), configOrMethodName);
		});
	}
	return collection;
}

/**
 * Checks if the given method is valid. A method is valid if it exists and isn't
 * private.
 * @param {!Object} instance The instance to check for the method.
 * @param {string} methodName The name of the method to check.
 * @return {boolean}
 */
function isValidMethod(instance, methodName) {
	return core.isFunction(instance[methodName]) &&
		methodName[0] !== '_' &&
		methodName[methodName.length - 1] !== '_';
}

/**
 * Called when an event is triggered on a Metal component that has been registered
 * as a jQuery plugin. Triggers a similar event on the jQuery element tied to the
 * plugin.
 * @param {string} name The name of the plugin.
 * @param {!jQuery} element A jQuery collection with a single element.
 * @param {string} eventType The name of the Metal.js event type.
 * @param {*} eventData Event data that was passed to the listener of the Metal.js
 *   event.
 */
function onMetalEvent(name, element, eventType, eventData) {
	var fullName = getPluginFullName(name);
	element.trigger(fullName + ':' + eventType, eventData);
}

export default JQueryAdapter;
<commit_msg>Fixes documentation to fit the standard<commit_after>'use strict';

import core from 'bower:metal/src/core';

/**
 * Acts as a bridge between Metal.js and jQuery, allowing Metal.js components to
 * be used as jQuery plugins.
 * @type {!Object}
 */
var JQueryAdapter = {
	/**
	 * Registers a Metal.js component as a jQuery plugin with the given name.
	 * @param {string} name The name of the plugin that should be registered.
	 * @param {!Function(Object)} Ctor The constructor of the Metal.js component.
	 */
	register(name, Ctor) {
		if (!$) {
			throw new Error('jQuery needs to be included in the page for JQueryAdapter to work.');
		}
		if (!core.isString(name)) {
			throw new Error('The name string is required for registering a plugin');
		}
		if (!core.isFunction(Ctor)) {
			throw new Error('The constructor function is required for registering a plugin');
		}

		$.fn[name] = function(configOrMethodName) {
			var args = Array.prototype.slice.call(arguments, 1);
			return handlePluginCall(name, Ctor, this, configOrMethodName, args);
		};
	}
};

/**
 * Calls a method on the plugin instance for the given element.
 * @param {string} name The name of the plugin.
 * @param {!jQuery} element A jQuery collection with a single element.
 * @param {string} methodName The name of the method to be called.
 * @param {Array} args The arguments to call the method with.
 * @return {*} The return value of the called method.
 */
function callMethod(name, element, methodName, args) {
	var fullName = getPluginFullName(name);
	var instance = element.data(fullName);
	if (!instance) {
		throw new Error('Tried to call method ' + methodName + ' on ' + name + ' plugin' +
			'without initialing it first.');
	}
	if (!isValidMethod(instance, methodName)) {
		throw new Error('Plugin ' + name + ' has no method called ' + methodName);
	}
	return instance[methodName].apply(instance, args);
}

/**
 * Creates an instace of a component for the given element, or updates it if one
 * already exists.
 * @param {string} name The name of the plugin.
 * @param {!Function(Object)} Ctor The constructor of the Metal.js component.
 * @param {!jQuery} element A jQuery collection with a single element.
 * @param {Object} config A config object to be passed to the component instance.
 */
function createOrUpdateInstance(name, Ctor, element, config) {
	var fullName = getPluginFullName(name);
	var instance = element.data(fullName);
	config = $.extend({}, config, {
		element: element[0]
	});
	if (instance) {
		instance.setAttrs(config);
	} else {
		instance = new Ctor(config).render();
		instance.on('*', onMetalEvent.bind(null, name, element));
		element.data(fullName, instance);
	}
}

/**
 * Gets the full name of the given plugin, by appending a prefix to it.
 * @param {string} name The name of the plugin.
 * @return {string}
 */
function getPluginFullName(name) {
	return 'metal-' + name;
}

/**
 * Handles calls to a registered plugin.
 * @param {string} name The name of the plugin.
 * @param {!Function(Object)} Ctor The constructor of the Metal.js component.
 * @param {!jQuery} collection A jQuery collection of elements to handle the plugin for.
 * @param {?(string|Object)} configOrMethodName If this is a string, a method with
 * that name will be called on the appropriate component instance. Otherwise, an
 * the instance (which will be created if it doesn't yet exist) will receive this
 * as its config object.
 * @param {Array} args All other arguments that were passed to the plugin call.
 */
function handlePluginCall(name, Ctor, collection, configOrMethodName, args) {
	if (core.isString(configOrMethodName)) {
		return callMethod(name, $(collection[0]), configOrMethodName, args);
	} else {
		collection.each(function() {
			createOrUpdateInstance(name, Ctor, $(this), configOrMethodName);
		});
	}
	return collection;
}

/**
 * Checks if the given method is valid. A method is valid if it exists and isn't
 * private.
 * @param {!Object} instance The instance to check for the method.
 * @param {string} methodName The name of the method to check.
 * @return {boolean}
 */
function isValidMethod(instance, methodName) {
	return core.isFunction(instance[methodName]) &&
		methodName[0] !== '_' &&
		methodName[methodName.length - 1] !== '_';
}

/**
 * Called when an event is triggered on a Metal component that has been registered
 * as a jQuery plugin. Triggers a similar event on the jQuery element tied to the
 * plugin.
 * @param {string} name The name of the plugin.
 * @param {!jQuery} element A jQuery collection with a single element.
 * @param {string} eventType The name of the Metal.js event type.
 * @param {*} eventData Event data that was passed to the listener of the Metal.js
 *   event.
 */
function onMetalEvent(name, element, eventType, eventData) {
	var fullName = getPluginFullName(name);
	element.trigger(fullName + ':' + eventType, eventData);
}

export default JQueryAdapter;
<|endoftext|>


<commit_before>const Botkit = require('botkit');

const StatusHandler = require('../statusHandler');

// video providers
const PROVIDERS = [
    require("../providers/youtube")
];

const Playlist = require('./playlist');

function start(app, options) {
    // Init the bot controller
    const controller = Botkit.slackbot({
        stats_optout: true,
        debug: false
        // logLevel: 7
        //include "log: false" to disable logging
        //or a "logLevel" integer from 0 to 7 to adjust logging verbosity
    });

    // Init the bot
    let bot = controller.spawn({
        token: options.slack.token,
        retry: 10
    });

    bot.startRTM(function (err) {
        if (err) {
            console.error(err);
            StatusHandler.setHealthy(false);
        } else {
            StatusHandler.setHealthy(true);
        }
    });

    // Setup webhook endpoints
    // controller.config.hostname = options.hostname;
    // controller.config.port = options.port;
    // controller.createWebhookEndpoints(app);
    const url = getURL(options);

    // TODO: allow multiple playlist instances
    const pl = Playlist.getInstance();

    // Set listeners
    PROVIDERS.forEach(provider => {
        controller.hears([provider.PATTERN], ['direct_mention'], function (bot, message) {

            provider.process(message)
                .then(
                    video => {
                        let added = pl.addVideo(message.user, video);

                        if (added) {
                            console.info(`Video added to queue: ${video.id}`);

                            bot.say({
                                text: `<@${message.user}>: added video to queue - <${video.url}|${video.title}>`,
                                unfurl_links: false,
                                unfurl_media: false,
                                channel: message.channel
                            });
                        } else {
                            console.info(`Video rejected: ${video.id}`);

                            bot.say({
                                text: `<@${message.user}>: Computer says no! I don't like that video, give me something new.`,
                                channel: message.channel
                            });
                        }
                    }
                )
                .catch(console.error.bind(console));

            bot.startTyping(message);
        });
    });

    controller.hears(['skip'], ['direct_mention'], exactMatch, function (bot, message) {
        let voted = pl.voteToSkip(message.user);

        if (voted) {
            bot.reply(message, `<@${message.user}> voted to skip`);
        } else {
            bot.reply(message, `<@${message.user}>: No vote for you!`);
        }
    });

    controller.hears(['link me', 'linkme', 'url', 'link'], ['direct_mention', 'direct_message'], exactMatch, function (bot, message) {
        bot.reply(message, `<@${message.user}> the url is: <${url}>`);
    });

    controller.hears(['help', 'helpme', 'help me'], ['direct_mention', 'direct_message'], exactMatch, function (bot, message) {
        bot.reply(message, {
            attachments: [{
                fallback: "list of available commands",
                title: "Available commands",
                text: "*<video_url>*: will add a video url to the queue\n" +
                "*skip*: will vote to skip the current track\n" +
                "*link me*: will print the link to the player\n" +
                "*what’s playing?*: will print the currently playing song\n" +
                "*help*: shows this help screen",
                mrkdwn_in: ["text"]
            }]
        });
    });

    controller.hears([/what[’|']?s playing\??/, /what is playing\??/, 'now playing'], ['direct_mention', 'direct_message'], function (bot, message) {
        let response = getNowPlayingMessage(
            message.user,
            pl.getCurrent()
        );

        bot.reply(message, response);
    });

    pl.on('video_skip', function (entry, votes) {
        // Video was skipped, say it in all channels
        toAllChannels(bot, channels => {
            channels.map(entry => entry['id']).forEach(id => {
                bot.say({
                    text: `Skipping video <${entry.video.url}|${entry.video.title}>`,
                    attachments: [{
                        title: "Votes:",
                        text: votes.map(id => `<@${id}>`).join(", ")
                    }],
                    channel: id
                })
            });
        });
    });

    pl.on('video_change', function (entry) {
        // New video, say it in all channels
        toAllChannels(bot, channels => {
            let message = getNextVideoMessage(entry);

            channels.map(entry => entry['id']).forEach(id => {
                bot.say(Object.assign({}, message, {
                    channel: id
                }));
            });
        });
    });

    // controller.on('slash_command', function(bot, message) {
    //     console.log(message);
    // });

    controller.on('rtm_reconnect_failed', function () {
        StatusHandler.setHealthy(false);
        console.error("Reconnection to slack failed");
    });

    return controller;
}

function getURL(options) {
    if (options.url) {
        return options.url;
    }

    return `http://${options.hostname}${options.port !== 80 ? ":" + options.port : ""}` + (options.path || "");
}

function exactMatch(patterns, message) {
    return patterns.indexOf(message.text) !== -1;
}

function toAllChannels(bot, cb) {
    bot.api.channels.list({}, (err, data) => {
        if (err || data['ok'] !== true) {
            console.error("There was an error listing channels", err);
        } else {
            let channels = [].concat(data.channels || [])
                .filter(entry => entry['is_member']);

            cb(channels);
        }
    });
}

function getNextVideoMessage(entry) {
    if (entry) {
        return {
            text: `Now playing: <${entry.video.url}|${entry.video.title}> (submitted by <${entry.user}>)`,
            unfurl_links: false,
            unfurl_media: false
        };
    } else {
        return {
            text: "No video to play next. Maybe you guys could give me a link or two."
        };
    }
}

function getNowPlayingMessage(userID, entry) {
    if (entry) {
        return {
            text: `<@${userID}> the video playing is <${entry.video.url}|${entry.video.title}>.`,
            unfurl_links: false,
            unfurl_media: false
        };
    } else {
        return {
            text: `<@${userID}> nothing's playing, maybe you can give me a video or two.`
        };
    }
}

module.exports = {
    start: start
};<commit_msg>Fix issue with now playing notification<commit_after>const Botkit = require('botkit');

const StatusHandler = require('../statusHandler');

// video providers
const PROVIDERS = [
    require("../providers/youtube")
];

const Playlist = require('./playlist');

function start(app, options) {
    // Init the bot controller
    const controller = Botkit.slackbot({
        stats_optout: true,
        debug: false
        // logLevel: 7
        //include "log: false" to disable logging
        //or a "logLevel" integer from 0 to 7 to adjust logging verbosity
    });

    // Init the bot
    let bot = controller.spawn({
        token: options.slack.token,
        retry: 10
    });

    bot.startRTM(function (err) {
        if (err) {
            console.error(err);
            StatusHandler.setHealthy(false);
        } else {
            StatusHandler.setHealthy(true);
        }
    });

    // Setup webhook endpoints
    // controller.config.hostname = options.hostname;
    // controller.config.port = options.port;
    // controller.createWebhookEndpoints(app);
    const url = getURL(options);

    // TODO: allow multiple playlist instances
    const pl = Playlist.getInstance();

    // Set listeners
    PROVIDERS.forEach(provider => {
        controller.hears([provider.PATTERN], ['direct_mention'], function (bot, message) {

            provider.process(message)
                .then(
                    video => {
                        let added = pl.addVideo(message.user, video);

                        if (added) {
                            console.info(`Video added to queue: ${video.id}`);

                            bot.say({
                                text: `<@${message.user}>: added video to queue - <${video.url}|${video.title}>`,
                                unfurl_links: false,
                                unfurl_media: false,
                                channel: message.channel
                            });
                        } else {
                            console.info(`Video rejected: ${video.id}`);

                            bot.say({
                                text: `<@${message.user}>: Computer says no! I don't like that video, give me something new.`,
                                channel: message.channel
                            });
                        }
                    }
                )
                .catch(console.error.bind(console));

            bot.startTyping(message);
        });
    });

    controller.hears(['skip'], ['direct_mention'], exactMatch, function (bot, message) {
        let voted = pl.voteToSkip(message.user);

        if (voted) {
            bot.reply(message, `<@${message.user}> voted to skip`);
        } else {
            bot.reply(message, `<@${message.user}>: No vote for you!`);
        }
    });

    controller.hears(['link me', 'linkme', 'url', 'link'], ['direct_mention', 'direct_message'], exactMatch, function (bot, message) {
        bot.reply(message, `<@${message.user}> the url is: <${url}>`);
    });

    controller.hears(['help', 'helpme', 'help me'], ['direct_mention', 'direct_message'], exactMatch, function (bot, message) {
        bot.reply(message, {
            attachments: [{
                fallback: "list of available commands",
                title: "Available commands",
                text: "*<video_url>*: will add a video url to the queue\n" +
                "*skip*: will vote to skip the current track\n" +
                "*link me*: will print the link to the player\n" +
                "*what’s playing?*: will print the currently playing song\n" +
                "*help*: shows this help screen",
                mrkdwn_in: ["text"]
            }]
        });
    });

    controller.hears([/what[’|']?s playing\??/, /what is playing\??/, 'now playing'], ['direct_mention', 'direct_message'], function (bot, message) {
        let response = getNowPlayingMessage(
            message.user,
            pl.getCurrent()
        );

        bot.reply(message, response);
    });

    pl.on('video_skip', function (entry, votes) {
        // Video was skipped, say it in all channels
        toAllChannels(bot, channels => {
            channels.map(entry => entry['id']).forEach(id => {
                bot.say({
                    text: `Skipping video <${entry.video.url}|${entry.video.title}>`,
                    attachments: [{
                        title: "Votes:",
                        text: votes.map(id => `<@${id}>`).join(", ")
                    }],
                    channel: id
                })
            });
        });
    });

    pl.on('video_change', function (entry) {
        // New video, say it in all channels
        toAllChannels(bot, channels => {
            let message = getNextVideoMessage(entry);

            channels.map(entry => entry['id']).forEach(id => {
                bot.say(Object.assign({}, message, {
                    channel: id
                }));
            });
        });
    });

    // controller.on('slash_command', function(bot, message) {
    //     console.log(message);
    // });

    controller.on('rtm_reconnect_failed', function () {
        StatusHandler.setHealthy(false);
        console.error("Reconnection to slack failed");
    });

    return controller;
}

function getURL(options) {
    if (options.url) {
        return options.url;
    }

    return `http://${options.hostname}${options.port !== 80 ? ":" + options.port : ""}` + (options.path || "");
}

function exactMatch(patterns, message) {
    return patterns.indexOf(message.text) !== -1;
}

function toAllChannels(bot, cb) {
    bot.api.channels.list({}, (err, data) => {
        if (err || data['ok'] !== true) {
            console.error("There was an error listing channels", err);
        } else {
            let channels = [].concat(data.channels || [])
                .filter(entry => entry['is_member']);

            cb(channels);
        }
    });
}

function getNextVideoMessage(entry) {
    if (entry) {
        return {
            text: `Now playing: <${entry.video.url}|${entry.video.title}> (submitted by <@${entry.user}>)`,
            unfurl_links: false,
            unfurl_media: false
        };
    } else {
        return {
            text: "No video to play next. Maybe you guys could give me a link or two."
        };
    }
}

function getNowPlayingMessage(userID, entry) {
    if (entry) {
        return {
            text: `<@${userID}> the video playing is <${entry.video.url}|${entry.video.title}>.`,
            unfurl_links: false,
            unfurl_media: false
        };
    } else {
        return {
            text: `<@${userID}> nothing's playing, maybe you can give me a video or two.`
        };
    }
}

module.exports = {
    start: start
};<|endoftext|>


<commit_before>import groupBy from 'lodash/groupBy';
import mean from 'lodash/mean';
import meanBy from 'lodash/meanBy';
import concat from 'lodash/concat';
import maxBy from 'lodash/maxBy';
import minBy from 'lodash/minBy';
import upperCase from 'lodash/upperCase';
import moment from 'moment';
import range from 'lodash/range';

const translateMeans = (means, latest) => {
  if (!means || !means.length) return null;
  const currentWeek = moment(latest).isoWeek();
  const firstHalf = means.slice(0, currentWeek);
  const secondHalf = means.slice(currentWeek);
  return secondHalf.concat(firstHalf);
};

const getYearsObj = (data, startSlice, endSlice) => {
  const grouped = groupBy(data, 'year');
  return Object.keys(grouped).map(key => ({
    year: key,
    weeks: grouped[key].slice(
      startSlice < 0 ? grouped[key].length + startSlice : startSlice,
      endSlice < 0 ? grouped[key].length : endSlice
    )
  }));
};

const meanData = data => {
  const means = [];
  data.forEach(w => {
    w.weeks.forEach((y, i) => {
      means[i] = means[i] ? [...means[i], y.count] : [y.count];
    });
  });
  return means.map(w => mean(w));
};

const runningMean = (data, windowSize) => {
  const smoothedMean = [];
  data.forEach((d, i) => {
    const slice = data.slice(i, i + windowSize);
    if (i < data.length - windowSize + 1) {
      smoothedMean.push(mean(slice));
    }
  });
  return smoothedMean;
};

export const getMeansData = (data, latest) => {
  const minYear = minBy(data, 'year').year;
  const maxYear = maxBy(data, 'year').year;
  const grouped = groupBy(data, 'week');
  const centralMeans = Object.keys(grouped).map(d => {
    const weekData = grouped[d];
    return meanBy(weekData, 'count');
  });
  const leftYears = data.filter(d => d.year !== maxYear);
  const rightYears = data.filter(d => d.year !== minYear);
  const leftMeans = meanData(getYearsObj(leftYears, -6));
  const rightMeans = meanData(getYearsObj(rightYears, 0, 6));
  const allMeans = concat(leftMeans, centralMeans, rightMeans);
  const smoothedMeans = runningMean(allMeans, 12);
  const translatedMeans = translateMeans(smoothedMeans, latest);
  const pastYear = data.slice(-52);
  const parsedData = pastYear.map((d, i) => ({
    ...d,
    mean: (translatedMeans && translatedMeans[i]) || 0
  }));
  return parsedData;
};

export const getStdDevData = (data, rawData) => {
  const stdDevs = [];
  const centralMeans = data.map(d => d.mean);
  const groupedByYear = groupBy(rawData, 'year');
  const meansFromGroup = Object.keys(groupedByYear).map(key =>
    groupedByYear[key].map(d => d.count)
  );
  for (let i = 0; i < centralMeans.length; i += 1) {
    meansFromGroup.forEach(m => {
      const value = m[i] || 0;
      const some =
        value && centralMeans[i] ? (centralMeans[i] - value) ** 2 : null;
      stdDevs[i] = stdDevs[i] ? [...stdDevs[i], some] : [some];
    });
  }
  const stdDev = mean(stdDevs.map(s => mean(s) ** 0.5));

  return data.map(d => ({
    ...d,
    plusStdDev: [d.mean, d.mean + stdDev],
    minusStdDev: [d.mean - stdDev, d.mean],
    twoPlusStdDev: [d.mean + stdDev, d.mean + stdDev * 2],
    twoMinusStdDev: [d.mean - stdDev * 2, d.mean - stdDev]
  }));
};

export const getDatesData = data =>
  data.map(d => ({
    ...d,
    date: moment()
      .year(d.year)
      .isoWeek(d.week)
      .startOf('isoWeek')
      .format('YYYY-MM-DD'),
    month: upperCase(
      moment()
        .year(d.year)
        .isoWeek(d.week)
        .format('MMM')
    )
  }));

export const getChartConfig = (colors, latest, unit = '') => {
  const ticks = [];
  while (ticks.length < 12) {
    ticks.push(
      parseInt(
        moment(latest)
          .subtract(ticks.length, 'months')
          .format('Mo'),
        10
      )
    );
  }
  return {
    xKey: 'date',
    yKeys: {
      lines: {
        count: {
          stroke: colors.main
        },
        target: { stroke: 'grey' }
      },
      areas: {
        plusStdDev: {
          fill: '#555555',
          stroke: '#555555',
          opacity: 0.1,
          strokeWidth: 0,
          background: false,
          activeDot: false
        },
        minusStdDev: {
          fill: '#555555',
          stroke: '#555555',
          opacity: 0.1,
          strokeWidth: 0,
          background: false,
          activeDot: false
        },
        twoPlusStdDev: {
          fill: '#555555',
          stroke: '#555555',
          opacity: 0.2,
          strokeWidth: 0,
          background: false,
          activeDot: false
        },
        twoMinusStdDev: {
          fill: '#555555',
          stroke: '#555555',
          opacity: 0.2,
          strokeWidth: 0,
          background: false,
          activeDot: false
        }
      }
    },
    xAxis: {
      tickCount: 12,
      interval: 4,
      tickFormatter: t => moment(t).format('MMM')
    },
    yAxis: {
      domain: [0, 'auto'],
      allowDataOverflow: true
    },
    height: '280px',
    unit
  };
};

export const yearTicksFormatter = (tick, startYear, endYear) => {
  const year = moment(tick, 'YYYY');
  if (tick === startYear || tick === endYear) {
    return year.format('YYYY');
  }
  return `'${year.format('YY')}`;
};

export const getYearsRange = (data, interval) => {
  const startYearObj = minBy(data, 'year');
  const endYearObj = maxBy(data, 'year');
  const startYear = startYearObj && startYearObj.year;
  const endYear = endYearObj && endYearObj.year;

  return {
    startYear,
    endYear,
    range: range(startYear, endYear + 1, interval || 1).map(y => ({
      label: y,
      value: y
    }))
  };
};

export const getYearsRangeFromMinMax = (yearMin, yearMax, interval) => ({
  startYear: yearMin,
  endYear: yearMax,
  range: range(yearMin, yearMax + 1, interval || 1).map(y => ({
    label: y,
    value: y
  }))
});
<commit_msg>Adds helper function<commit_after>import groupBy from 'lodash/groupBy';
import mean from 'lodash/mean';
import meanBy from 'lodash/meanBy';
import concat from 'lodash/concat';
import maxBy from 'lodash/maxBy';
import minBy from 'lodash/minBy';
import upperCase from 'lodash/upperCase';
import moment from 'moment';
import range from 'lodash/range';

const translateMeans = (means, latest) => {
  if (!means || !means.length) return null;
  const currentWeek = moment(latest).isoWeek();
  const firstHalf = means.slice(0, currentWeek);
  const secondHalf = means.slice(currentWeek);
  return secondHalf.concat(firstHalf);
};

const getYearsObj = (data, startSlice, endSlice) => {
  const grouped = groupBy(data, 'year');
  return Object.keys(grouped).map(key => ({
    year: key,
    weeks: grouped[key].slice(
      startSlice < 0 ? grouped[key].length + startSlice : startSlice,
      endSlice < 0 ? grouped[key].length : endSlice
    )
  }));
};

const meanData = data => {
  const means = [];
  data.forEach(w => {
    w.weeks.forEach((y, i) => {
      means[i] = means[i] ? [...means[i], y.count] : [y.count];
    });
  });
  return means.map(w => mean(w));
};

const runningMean = (data, windowSize) => {
  const smoothedMean = [];
  data.forEach((d, i) => {
    const slice = data.slice(i, i + windowSize);
    if (i < data.length - windowSize + 1) {
      smoothedMean.push(mean(slice));
    }
  });
  return smoothedMean;
};

export const getMeansData = (data, latest) => {
  const minYear = minBy(data, 'year').year;
  const maxYear = maxBy(data, 'year').year;
  const grouped = groupBy(data, 'week');
  const centralMeans = Object.keys(grouped).map(d => {
    const weekData = grouped[d];
    return meanBy(weekData, 'count');
  });
  const leftYears = data.filter(d => d.year !== maxYear);
  const rightYears = data.filter(d => d.year !== minYear);
  const leftMeans = meanData(getYearsObj(leftYears, -6));
  const rightMeans = meanData(getYearsObj(rightYears, 0, 6));
  const allMeans = concat(leftMeans, centralMeans, rightMeans);
  const smoothedMeans = runningMean(allMeans, 12);
  const translatedMeans = translateMeans(smoothedMeans, latest);
  const pastYear = data.slice(-52);
  const parsedData = pastYear.map((d, i) => ({
    ...d,
    mean: (translatedMeans && translatedMeans[i]) || 0
  }));
  return parsedData;
};

export const getStdDevData = (data, rawData) => {
  const stdDevs = [];
  const centralMeans = data.map(d => d.mean);
  const groupedByYear = groupBy(rawData, 'year');
  const meansFromGroup = Object.keys(groupedByYear).map(key =>
    groupedByYear[key].map(d => d.count)
  );
  for (let i = 0; i < centralMeans.length; i += 1) {
    meansFromGroup.forEach(m => {
      const value = m[i] || 0;
      const some =
        value && centralMeans[i] ? (centralMeans[i] - value) ** 2 : null;
      stdDevs[i] = stdDevs[i] ? [...stdDevs[i], some] : [some];
    });
  }
  const stdDev = mean(stdDevs.map(s => mean(s) ** 0.5));

  return data.map(d => ({
    ...d,
    plusStdDev: [d.mean, d.mean + stdDev],
    minusStdDev: [d.mean - stdDev, d.mean],
    twoPlusStdDev: [d.mean + stdDev, d.mean + stdDev * 2],
    twoMinusStdDev: [d.mean - stdDev * 2, d.mean - stdDev]
  }));
};

export const getDatesData = data =>
  data.map(d => ({
    ...d,
    date: moment()
      .year(d.year)
      .isoWeek(d.week)
      .startOf('isoWeek')
      .format('YYYY-MM-DD'),
    month: upperCase(
      moment()
        .year(d.year)
        .isoWeek(d.week)
        .format('MMM')
    )
  }));

export const getChartConfig = (colors, latest, unit = '') => {
  const ticks = [];
  while (ticks.length < 12) {
    ticks.push(
      parseInt(
        moment(latest)
          .subtract(ticks.length, 'months')
          .format('Mo'),
        10
      )
    );
  }
  return {
    xKey: 'date',
    yKeys: {
      lines: {
        count: {
          stroke: colors.main
        },
        target: { stroke: 'grey' }
      },
      areas: {
        plusStdDev: {
          fill: '#555555',
          stroke: '#555555',
          opacity: 0.1,
          strokeWidth: 0,
          background: false,
          activeDot: false
        },
        minusStdDev: {
          fill: '#555555',
          stroke: '#555555',
          opacity: 0.1,
          strokeWidth: 0,
          background: false,
          activeDot: false
        },
        twoPlusStdDev: {
          fill: '#555555',
          stroke: '#555555',
          opacity: 0.2,
          strokeWidth: 0,
          background: false,
          activeDot: false
        },
        twoMinusStdDev: {
          fill: '#555555',
          stroke: '#555555',
          opacity: 0.2,
          strokeWidth: 0,
          background: false,
          activeDot: false
        }
      }
    },
    xAxis: {
      tickCount: 12,
      interval: 4,
      tickFormatter: t => moment(t).format('MMM')
    },
    yAxis: {
      domain: [0, 'auto'],
      allowDataOverflow: true
    },
    height: '280px',
    unit
  };
};

export const yearTicksFormatter = (tick, startYear, endYear) => {
  const year = moment(tick, 'YYYY');
  if (tick === startYear || tick === endYear) {
    return year.format('YYYY');
  }
  return `'${year.format('YY')}`;
};

export const getYearsRangeFromData = (data, interval) => {
  const startYearObj = minBy(data, 'year');
  const endYearObj = maxBy(data, 'year');
  const startYear = startYearObj && startYearObj.year;
  const endYear = endYearObj && endYearObj.year;

  return {
    startYear,
    endYear,
    range: range(startYear, endYear + 1, interval || 1).map(y => ({
      label: y,
      value: y
    }))
  };
};

export const getYearsRangeFromMinMax = (yearMin, yearMax, interval) => ({
  startYear: yearMin,
  endYear: yearMax,
  range: range(yearMin, yearMax + 1, interval || 1).map(y => ({
    label: y,
    value: y
  }))
});
<|endoftext|>


<commit_before>"use strict";

angular.module('arethusa.core').directive('keysToScreen', [
  '$timeout',
  function($timeout) {
    return {
      restrict: 'A',
      scope: {},
      link: function(scope, element, attrs) {
        scope.keys = [];

        function Key(key) {
          this.str = key === "PLUS" ? '+' : key;
          this.joiner = key === 'PLUS';
        }

        function parseKey(key) {
          var keys, elements;
          keys = (key + '').split('-'); // key can also be a number
          // This looks a bit ugly, but a regular + might also be a
          // valid keybinding - we therefore use an unambigous value here.
          keys = keys.join('-PLUS-').split('-');
          elements = arethusaUtil.inject([], keys, function(memo, key) {
            if (key.match(/^[A-Z]$/)) {
              arethusaUtil.pushAll(memo, ['shift', 'PLUS', key.toLowerCase()]);
            } else {
              memo.push(key);
            }
          });
          return arethusaUtil.map(elements, function(el) { return new Key(el); });
        }

        var clear, override, readyToOverride;
        scope.$on('keyCaptureLaunched', function(event, key) {
          var keys = parseKey(key);
          scope.$apply(function() {
            if (readyToOverride) {
              scope.keys = keys;
              readyToOverride = false;
            } else {
              arethusaUtil.pushAll(scope.keys, keys);
            }
          });

          if (override) $timeout.cancel(override);
          if (clear) $timeout.cancel(clear);

          override = $timeout(function() {
            readyToOverride = true;
          }, 1500);

          clear = $timeout(function() {
            scope.keys = [];
          }, 3200);
        });
      },
      templateUrl: 'templates/arethusa.core/keys_to_screen.html'
    };
  }
]);
<commit_msg>Allow to enable or disable keysToScreen<commit_after>"use strict";

angular.module('arethusa.core').directive('keysToScreen', [
  '$timeout',
  'configurator',
  function($timeout, configurator) {
    return {
      restrict: 'A',
      scope: {},
      link: function(scope, element, attrs) {
        var conf = configurator.configurationFor('main');

        function Key(key) {
          this.str = key === "PLUS" ? '+' : key;
          this.joiner = key === 'PLUS';
        }

        function parseKey(key) {
          var keys, elements;
          keys = (key + '').split('-'); // key can also be a number
          // This looks a bit ugly, but a regular + might also be a
          // valid keybinding - we therefore use an unambigous value here.
          keys = keys.join('-PLUS-').split('-');
          elements = arethusaUtil.inject([], keys, function(memo, key) {
            if (key.match(/^[A-Z]$/)) {
              arethusaUtil.pushAll(memo, ['shift', 'PLUS', key.toLowerCase()]);
            } else {
              memo.push(key);
            }
          });
          return arethusaUtil.map(elements, function(el) { return new Key(el); });
        }

        // This isn't updated know - it's either activated on startup, or
        // it isn't.
        if (conf.showKeys) {
          scope.keys = [];

          var clear, override, readyToOverride;
          scope.$on('keyCaptureLaunched', function(event, key) {
            var keys = parseKey(key);
            scope.$apply(function() {
              if (readyToOverride) {
                scope.keys = keys;
                readyToOverride = false;
              } else {
                arethusaUtil.pushAll(scope.keys, keys);
              }
            });

            if (override) $timeout.cancel(override);
            if (clear) $timeout.cancel(clear);

            override = $timeout(function() {
              readyToOverride = true;
            }, 1500);

            clear = $timeout(function() {
              scope.keys = [];
            }, 3200);
          });
        }

      },
      templateUrl: 'templates/arethusa.core/keys_to_screen.html'
    };
  }
]);
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react'
import { Link } from 'react-router'
import { connect } from 'react-redux'
import Modal from 'react-modal'

import { getWebAccountTypes } from '../../utils'

function mapStateToProps(state) {
  return {
    api: state.settings.api
  }
}

class SocialAccountItem extends Component {
  static propTypes = {
    listItem: PropTypes.bool.isRequired,
    service: PropTypes.string.isRequired,
    identifier: PropTypes.string.isRequired,
    proofUrl: PropTypes.string,
    verified: PropTypes.bool,
    api: PropTypes.object.isRequired
  }

  constructor(props) {
    super(props)

    this.getAccountUrl = this.getAccountUrl.bind(this)
    this.getIconClass = this.getIconClass.bind(this)
    this.getIdentifier = this.getIdentifier.bind(this)
  }

  getAccountUrl() {
    const webAccountTypes = getWebAccountTypes(this.props.api)
    let accountUrl = `http://${this.props.service}.com/${this.props.identifier}`
    if (webAccountTypes.hasOwnProperty(this.props.service)) {
      if (webAccountTypes[this.props.service].hasOwnProperty('urlTemplate')) {
        let urlTemplate = webAccountTypes[this.props.service].urlTemplate
        if (urlTemplate) {
          accountUrl = urlTemplate.replace('{identifier}', this.props.identifier)
        }
      }
    }
    return accountUrl
  }

  getIconClass() {
    const webAccountTypes = getWebAccountTypes(this.props.api)
    let iconClass = ''
    if (webAccountTypes.hasOwnProperty(this.props.service)) {
      iconClass = webAccountTypes[this.props.service].iconClass
    }
    return iconClass
  }

  getIdentifier() {
    let identifier = this.props.identifier
    if (identifier.length >= 15) {
      identifier = identifier.slice(0, 15) + '...'
    }
    return identifier
  }

  render() {
    const webAccountTypes = getWebAccountTypes(this.props.api)
    if (this.props.listItem === true) {
      return (

            {this.props.verified ?

              <i className={`fa ${this.getIconClass()} fa-stack-1x`} />

            :

              <i className={`fa ${this.getIconClass()} fa-stack-1x`} />

            }

              {this.getIdentifier()}

      )
    } else {
      return (

          <i className={`fa ${this.getIconClass()}`} />
          {this.getIdentifier()}

      )
    }
  }
}

export default connect(mapStateToProps, null)(SocialAccountItem)
<commit_msg>only try to render social accounts we know about #575<commit_after>import React, { Component, PropTypes } from 'react'
import { Link } from 'react-router'
import { connect } from 'react-redux'
import Modal from 'react-modal'

import { getWebAccountTypes } from '../../utils'

function mapStateToProps(state) {
  return {
    api: state.settings.api
  }
}

class SocialAccountItem extends Component {
  static propTypes = {
    listItem: PropTypes.bool.isRequired,
    service: PropTypes.string.isRequired,
    identifier: PropTypes.string.isRequired,
    proofUrl: PropTypes.string,
    verified: PropTypes.bool,
    api: PropTypes.object.isRequired
  }

  constructor(props) {
    super(props)

    this.getAccountUrl = this.getAccountUrl.bind(this)
    this.getIconClass = this.getIconClass.bind(this)
    this.getIdentifier = this.getIdentifier.bind(this)
  }

  getAccountUrl() {
    const webAccountTypes = getWebAccountTypes(this.props.api)
    let accountUrl = `http://${this.props.service}.com/${this.props.identifier}`
    if (webAccountTypes.hasOwnProperty(this.props.service)) {
      if (webAccountTypes[this.props.service].hasOwnProperty('urlTemplate')) {
        let urlTemplate = webAccountTypes[this.props.service].urlTemplate
        if (urlTemplate) {
          accountUrl = urlTemplate.replace('{identifier}', this.props.identifier)
        }
      }
    }
    return accountUrl
  }

  getIconClass() {
    const webAccountTypes = getWebAccountTypes(this.props.api)
    let iconClass = ''
    if (webAccountTypes.hasOwnProperty(this.props.service)) {
      iconClass = webAccountTypes[this.props.service].iconClass
    }
    return iconClass
  }

  getIdentifier() {
    let identifier = this.props.identifier
    if (identifier.length >= 15) {
      identifier = identifier.slice(0, 15) + '...'
    }
    return identifier
  }

  render() {
    const webAccountTypes = getWebAccountTypes(this.props.api)
    if (webAccountTypes[this.props.service]) {
      if (this.props.listItem === true) {
        return (

              {this.props.verified ?

                <i className={`fa ${this.getIconClass()} fa-stack-1x`} />

              :

                <i className={`fa ${this.getIconClass()} fa-stack-1x`} />

              }

                {this.getIdentifier()}

        )
      } else {
        return (

            <i className={`fa ${this.getIconClass()}`} />
            {this.getIdentifier()}

        )
      }
    } else {
      return (

      )
    }
  }
}

export default connect(mapStateToProps, null)(SocialAccountItem)
<|endoftext|>


<commit_before>/**
 * Created by norman.albusberger on 08.07.14.
 */
//--------------Init Objects------------------------------------
$.fn.extend({getWebsiteManager : function() {
    return null;
}
});
$(document).ready(function(){
    /**
     * Initialize the WebsiteManager to orchestrate the WasabiLib system.
     * @type {WebSiteManager}
     */
    var websiteManager = new WebSiteManager();
    $.fn.getWebsiteManager = function() {
        return websiteManager;
    };

    /**
     * Set the available event types which can be used to trigger WasabiLib requests.
     */
    websiteManager.setPossibleEvents({
        select: true
        , search: true
        , dblclick: true
        , mouseover: true
        , mouseout: true
        , click: true
        , mousedown: true
        , submit: true
        , blur: true
        , keyup: true
        , keydown: true
        , keypress: true
    });

    /**
     * Execute methods in a controlled environment.
     */
    websiteManager.setExecuteRemoteProcedureCallManager(new ExecuteRemoteProcedureCallManager());

    /**
     * Show JavaScript alert window.
     */
    websiteManager.getWasabiNotificationCenter().register(new Alert());

    /**
     * Add, replace or remove html elements in the DOM.
     */
    websiteManager.getWasabiNotificationCenter().register(new InnerHtml());

    /**
     * Change values of html element attributes.
     */
    websiteManager.getWasabiNotificationCenter().register(new DomManipulator());

    /**
     * Display one or many bootstrap modal windows.
     */
    websiteManager.getWasabiNotificationCenter().register(new ModalWindow());

    /**
     * Write develop informations into the web browsers console.
     */
    websiteManager.getWasabiNotificationCenter().register(new ConsoleLog());

    /**
     * Show Growl-like messages.
     */
    websiteManager.getWasabiNotificationCenter().register(new Gritter());

    /**
     * Trigger events received from the server.
     */
    websiteManager.getWasabiNotificationCenter().register(new TriggerEventManager());

    /**
     * Display results based on input in a suggest field.
     */
    websiteManager.getWasabiNotificationCenter().register(new Suggest());
//    websiteManager.getWasabiNotificationCenter().register(new DropzoneManager());

    /**
     * Callback for a elements to extract the informations of the data-json attribute only.
     */
    websiteManager.addCallback(new Callback("link", new DataExtractor()));

    /**
     * Callback for button elements to extract the informations of the data-json attribute only.
     */
    websiteManager.addCallback(new Callback("button", new DataExtractor()));

    /**
     * Callback for form elements with an external submit button to extract the informations of the form.
     */
    websiteManager.addCallback(new Callback("ext_form_submit", new External_Form()));

    /**
     * Callback for form elements to extract the informations of the form.
     */
    websiteManager.addCallback(new Callback("form", new Form()));

    /**
     * Callback for text fields to extract the informations from the value attribute.
     */
    websiteManager.addCallback(new Callback("text", new Text_Field()));

    /**
     * Callback for suggest fields to extract the informations from the value attribute. Additionally request are only
     * be send if a certain number of diggets are in the field and 300ms delay time expired.
     * @type {Callback}
     */
    var suggestCallback = new Callback("suggest", new Text_Field());
    /**
     * Condition to check if a certain number of digits are in the text field.
     * @type {Condition}
     */
    var condition = new Condition(function(event) {
        var _self = suggestCallback;
        var digits = _self.dataExtractor.getElement().attr('data-digits');
        return digits <= _self.dataExtractor.getTransmissionCapableContent().content.length && (event.which > 40 || event.which < 37);
    });
    /**
     * 300ms delay time.
     */
    condition.setTimeout(300);
    suggestCallback.setCondition(condition);
    websiteManager.addCallback(suggestCallback);

    /**
     * Start registering all needed event handlers.
     */
    websiteManager.registerEventHandler();

    /**
     * Set the possible methods which can be used by recipient objects.
     */
    websiteManager.getExecuteRemoteProcedureCallManager().setPossibleMethods({
        ACTION_TYPE_SHOW: "show"
        , ACTION_TYPE_HIDE: "hide"
        , ACTION_TYPE_MODAL: "modal"
        , ACTION_TYPE_SLIDEDOWN: "slideDown"
        , ACTION_TYPE_SLIDEUP: "slideUp"
        , ACTION_TYPE_REPLACE: "html"
        , ACTION_TYPE_APPEND: "append"
        , ACTION_TYPE_REMOVE: "empty"
        , ACTION_TYPE_CSS: "css"
        , ACTION_TYPE_FADEOUT: "fadeOut"
        , ACTION_TYPE_FADEIN: "fadeIn"
        , ACTION_TYPE_ATTR: "attr"
        , ACTION_TYPE_ADD_CLASS: "addClass"
        , ACTION_TYPE_REMOVE_CLASS: "removeClass"
        , ACTION_TYPE_TOGGLE_CLASS: "toggleClass"
        , ACTION_TYPE_HREF: "href"
        , ACTION_TYPE_DROPZONE_DISCOVER: "discover"
        , ACTION_TYPE_TRIGGER: "trigger"
        , ACTION_TYPE_REMOVE_ELEMENT : "remove"
        , ACTION_TYPE_JS_ALERT : "alert"
    });

    /**
     * Initialize the suggest field features.
     * @type {WasabiSuggestFeatures}
     */
    var suggest = new WasabiSuggestFeatures();
});<commit_msg>Update wasabi.init.js<commit_after>/**
 * Created by norman.albusberger on 08.07.14.
 */
//--------------Init Objects------------------------------------
$.fn.extend({getWebsiteManager : function() {
    return null;
}
});
$(document).ready(function(){
    /**
     * Initialize the WebsiteManager to orchestrate the WasabiLib system.
     * @type {WebSiteManager}
     */
    var websiteManager = new WebSiteManager();
    $.fn.getWebsiteManager = function() {
        return websiteManager;
    };

    /**
     * Set the available event types which can be used to trigger WasabiLib requests.
     */
    websiteManager.setPossibleEvents({
        select: true
        , search: true
        , dblclick: true
        , mouseover: true
        , mouseout: true
        , click: true
        , mousedown: true
        , submit: true
        , blur: true
        , keyup: true
        , keydown: true
        , keypress: true
    });

    /**
     * Execute methods in a controlled environment.
     */
    websiteManager.setExecuteRemoteProcedureCallManager(new ExecuteRemoteProcedureCallManager());

    /**
     * Show JavaScript alert window.
     */
    websiteManager.getWasabiNotificationCenter().register(new Alert());

    /**
     * Add, replace or remove html elements in the DOM.
     */
    websiteManager.getWasabiNotificationCenter().register(new InnerHtml());

    /**
     * Change values of html element attributes.
     */
    websiteManager.getWasabiNotificationCenter().register(new DomManipulator());

    /**
     * Display one or many bootstrap modal windows.
     */
    websiteManager.getWasabiNotificationCenter().register(new ModalWindow());

    /**
     * Write develop informations into the web browsers console.
     */
    websiteManager.getWasabiNotificationCenter().register(new ConsoleLog());

    /**
     * Show Growl-like messages.
     */
    websiteManager.getWasabiNotificationCenter().register(new Gritter());

    /**
     * Trigger events received from the server.
     */
    websiteManager.getWasabiNotificationCenter().register(new TriggerEventManager());

    /**
     * Display results based on input in a suggest field.
     */
    websiteManager.getWasabiNotificationCenter().register(new Suggest());
//    websiteManager.getWasabiNotificationCenter().register(new DropzoneManager());

    /**
     * Callback for a elements to extract the informations of the data-json attribute only.
     */
    websiteManager.addCallback(new Callback("link", new DataExtractor()));

    /**
     * Callback for button elements to extract the informations of the data-json attribute only.
     */
    websiteManager.addCallback(new Callback("button", new DataExtractor()));

    /**
     * Callback for form elements with an external submit button to extract the informations of the form.
     */
    websiteManager.addCallback(new Callback("ext_form_submit", new External_Form()));

    /**
     * Callback for form elements to extract the informations of the form.
     */
    websiteManager.addCallback(new Callback("form", new Form()));

    /**
     * Callback for text fields to extract the informations from the value attribute.
     */
    websiteManager.addCallback(new Callback("text", new Text_Field()));

    /**
     * Callback for suggest fields to extract the informations from the value attribute. Additionally request are only
     * be send if a certain number of diggets are in the field and 300ms delay time expired.
     * @type {Callback}
     */
    var suggestCallback = new Callback("suggest", new Text_Field());
    /**
     * Condition to check if a certain number of digits are in the text field.
     * @type {Condition}
     */
    var condition = new Condition(function(event) {
        var _self = suggestCallback;
        var digits = _self.dataExtractor.getElement().attr('data-digits');
        return digits <= _self.dataExtractor.getTransmissionCapableContent().content.length && (event.which > 40 || event.which < 37);
    });
    /**
     * 300ms delay time.
     */
    condition.setTimeout(300);
    suggestCallback.setCondition(condition);
    websiteManager.addCallback(suggestCallback);

    /**
     * Set the possible methods which can be used by recipient objects.
     */
    websiteManager.getExecuteRemoteProcedureCallManager().setPossibleMethods({
        ACTION_TYPE_SHOW: "show"
        , ACTION_TYPE_HIDE: "hide"
        , ACTION_TYPE_MODAL: "modal"
        , ACTION_TYPE_SLIDEDOWN: "slideDown"
        , ACTION_TYPE_SLIDEUP: "slideUp"
        , ACTION_TYPE_REPLACE: "html"
        , ACTION_TYPE_APPEND: "append"
        , ACTION_TYPE_REMOVE: "empty"
        , ACTION_TYPE_CSS: "css"
        , ACTION_TYPE_FADEOUT: "fadeOut"
        , ACTION_TYPE_FADEIN: "fadeIn"
        , ACTION_TYPE_ATTR: "attr"
        , ACTION_TYPE_ADD_CLASS: "addClass"
        , ACTION_TYPE_REMOVE_CLASS: "removeClass"
        , ACTION_TYPE_TOGGLE_CLASS: "toggleClass"
        , ACTION_TYPE_HREF: "href"
        , ACTION_TYPE_DROPZONE_DISCOVER: "discover"
        , ACTION_TYPE_TRIGGER: "trigger"
        , ACTION_TYPE_REMOVE_ELEMENT : "remove"
        , ACTION_TYPE_JS_ALERT : "alert"
    });

    /**
     * Initialize the suggest field features.
     * @type {WasabiSuggestFeatures}
     */
    var suggest = new WasabiSuggestFeatures();
});
<|endoftext|>


<commit_before>#!/usr/bin/env node

var fs = require('fs');
var _ = require('lodash');
var domain = require('domain');
var moment = require('moment');
var parser = require('nomnom');
var Promise = require("bluebird");
var GithubApi = require('github');
var linkParser = require('parse-link-header');
var ghauth = Promise.promisify(require('ghauth'));

// It might be faster to just go through commits on the branch
// instead of iterating over closed issues, look into this later.
//
// Even better yet. I might just be able to do this with git log.
// tags: git log --tags --simplify-by-decoration --format="%ci%n%d"
// prs: git log --grep="Merge pull request #" --format="%s%n%ci%n%b"

opts = parser
  .option('owner', {
    abbr: 'o'
  , help: 'owner of the Github repository'
  , required: true
  })
  .option('repository', {
    abbr: 'r'
  , help: 'name of the Github repository'
  , required: true
  })
  .option('branch', {
    abbr: 'b'
  , help: '(optional) name of the default branch'
  , default: 'master'
  })
  .option('tagname', {
    abbr: 'n'
  , help: '(optional) tag name for upcoming release'
  , default: 'upcoming'
  })
  .option('issuebody', {
    abbr: 'i'
  , help: '(optional) include the body of the issue'
  , flag: true
  })
  .option('auth', {
    abbr: 'a'
  , help: '(optional) prompt to auth with Github - use this for private repos and higher rate limits'
  , flag: true
  })
  .option('token', {
    abbr: 'k'
  , help: '(optional) need to use this or --auth for private repos and higher rate limits'
  })
  .option('file', {
    abbr: 'f'
  , help: '(optional) name of the file to output the changelog to'
  , default: 'CHANGELOG.md'
  })
  .option('verbose', {
    abbr: 'v'
  , help: 'output details'
  , flag: true
  })
  // TODO
  // .option('template', {
  //   abbr: 't'
  // , help: '(optional) template to use to generate the changelog'
  // })
  .parse()
;

var currentDate = moment();

var github = new GithubApi({
  version: '3.0.0'
, timeout: 10000
});

// github auth token
var token = null;

// ~/.config/changelog.json will store the token
var authOptions = {
  configName : 'changelog'
, scopes     : ['user', 'public_repo', 'repo']
};

Promise.promisifyAll(github.repos);
Promise.promisifyAll(github.issues);
Promise.promisifyAll(github.pullRequests);

var getTags = function(){
  var tagOpts = {
    user: opts.owner
  , repo: opts.repository
  };
  auth();
  return github.repos.getTagsAsync(tagOpts).map(function(ref){
    auth();
    return github.repos.getCommitAsync({
      user: tagOpts.user
    , repo: tagOpts.repo
    , sha: ref.commit.sha
    }).then(function(commit){
      return {
        name: ref.name
      , date: moment(commit.commit.committer.date)
      };
    });
  }).then(function(tags){
    return tags;
  });
};

var getPullRequests = function(){

  var issueOpts = {
    user: opts.owner
  , repo: opts.repository
  , state: 'closed'
  , sort: 'updated'
  , direction: 'desc'
  , per_page: 100
  , page: 1
  // , since: null // TODO: this is an improvement to save API calls
  };

  var getIssues = function(options){
    auth();
    return github.issues.repoIssuesAsync(options).then(function(issues){
      opts.verbose && console.log('issues pulled - ', issues.length);
      opts.verbose && console.log('issues page - ', options.page);
      return issues;
    });
  };

  return getIssues(issueOpts).then(function(issues){
    var linkHeader = linkParser(issues.meta.link)
    var totalPages = (linkHeader && linkHeader.last) ? linkHeader.last.page : 1;

    if (totalPages > issueOpts.page) {
      var allReqs = [];
      for(var i=issueOpts.page; i<totalPages; i++){
        var newOptions = _.clone(issueOpts, true);
        newOptions.page += i;
        allReqs.push(getIssues(newOptions));
      }
      return Promise.all(allReqs).reduce(function(issues, moreIssues){
        return issues.concat(moreIssues);
      }, issues);
    }
    return issues;
  }).map(function(issue){
    if (!issue.pull_request.html_url) return;

    auth();
    return github.pullRequests.getAsync({
      user: issueOpts.user
    , repo: issueOpts.repo
    , number: issue.number
    }).then(function(pr){
      if (pr.base.ref !== opts.branch) return;
      if (!pr.merged_at) return;
      return {
        title: pr.title
      , body: pr.body
      , number: pr.number
      , html_url: pr.html_url
      , 'merged_at': moment(pr.merged_at)
      , user: {login: (pr.user||0).login || null}
      };
    });
  }).reduce(function(scrubbed, pr){
    if (pr) scrubbed.push(pr);
    return scrubbed;
  }, [])
  .then(function(prs){
    return prs;
  });
};

// sortedTags must be an array of tag data sorted by tag date DESC
var tagPr = function(sortedTags, pr) {
  var current = null;
  for (var i=0, len=sortedTags.length; i < len; i++) {
    var tag = sortedTags[i];
    if (tag.date < pr.merged_at) break;
    current = tag;
  }
  if (!current) current = {name: opts.tagname, date: currentDate};
  return current;
};

var formatter = function(data) {
  var currentTagName = '';
  var output = "## Change Log\n";
  data.forEach(function(pr){
    if (pr.tag === null) {
      currentTagName = opts.tagname;
      output+= "\n### " + opts.tagname;
      output+= "\n";
    } else if (pr.tag.name != currentTagName) {
      currentTagName = pr.tag.name;
      output+= "\n### " + pr.tag.name
      output+= " (" + pr.tag.date.utc().format("YYYY/MM/DD HH:mm Z") + ")";
      output+= "\n";
    }

    output += "- [#" + pr.number + "](" + pr.html_url + ") " + pr.title
    if (pr.user.login) output += " (@" + pr.user.login + ")";
    if (opts.issuebody && pr.body && pr.body.trim()) output += "\n\n    >" + pr.body.trim().replace(/\n/ig, "\n    > ");
    output += "\n\n";
  });
  return output.trim();
};

var getGithubToken = function() {
  if (opts.token) return Promise.resolve({token: opts.token});
  if (opts.auth) return ghauth(authOptions);
  return Promise.resolve({});
};

var auth = function() {
  if (!token) return;
  github.authenticate({type: 'oauth', token: token});
};

var task = function() {
  getGithubToken()
    .then(function(authData){
      if (!authData.token) return;
      token = authData.token;
    })
    .then(getTags)
    .then(function(tags){
      allTags = _.sortBy(tags, 'date').reverse();
      return;
    })
    .then(getPullRequests)
    .map(function(pr){
      pr.tag = tagPr(allTags, pr);
      pr.tagDate = pr.tag.date;
      return pr;
    })
    .then(function(data){
      data = _.sortBy(data, 'tagDate').reverse();
      return data;
    })
    .then(function(data){
      fs.writeFileSync(opts.file, formatter(data));
    })
    .then(function(){
      process.exit(0);
    })
    .catch(function(error){
      console.error('error', error);
      console.error('stack', error.stack);
      process.exit(1);
    })
  ;
};

var done = function (error) {
  if (!error) process.exit(0);
  console.log(error);
  console.log(error.stack);
  process.exit(1);
};

var runner = function () {
  var d = domain.create();
  d.on('error', done);
  d.run(task);
};

runner();<commit_msg>get tags and pull requests in parallel<commit_after>#!/usr/bin/env node

var fs = require('fs');
var _ = require('lodash');
var domain = require('domain');
var moment = require('moment');
var parser = require('nomnom');
var Promise = require("bluebird");
var GithubApi = require('github');
var linkParser = require('parse-link-header');
var ghauth = Promise.promisify(require('ghauth'));

// It might be faster to just go through commits on the branch
// instead of iterating over closed issues, look into this later.
//
// Even better yet. I might just be able to do this with git log.
// tags: git log --tags --simplify-by-decoration --format="%ci%n%d"
// prs: git log --grep="Merge pull request #" --format="%s%n%ci%n%b"

opts = parser
  .option('owner', {
    abbr: 'o'
  , help: 'owner of the Github repository'
  , required: true
  })
  .option('repository', {
    abbr: 'r'
  , help: 'name of the Github repository'
  , required: true
  })
  .option('branch', {
    abbr: 'b'
  , help: '(optional) name of the default branch'
  , default: 'master'
  })
  .option('tagname', {
    abbr: 'n'
  , help: '(optional) tag name for upcoming release'
  , default: 'upcoming'
  })
  .option('issuebody', {
    abbr: 'i'
  , help: '(optional) include the body of the issue'
  , flag: true
  })
  .option('auth', {
    abbr: 'a'
  , help: '(optional) prompt to auth with Github - use this for private repos and higher rate limits'
  , flag: true
  })
  .option('token', {
    abbr: 'k'
  , help: '(optional) need to use this or --auth for private repos and higher rate limits'
  })
  .option('file', {
    abbr: 'f'
  , help: '(optional) name of the file to output the changelog to'
  , default: 'CHANGELOG.md'
  })
  .option('verbose', {
    abbr: 'v'
  , help: 'output details'
  , flag: true
  })
  // TODO
  // .option('template', {
  //   abbr: 't'
  // , help: '(optional) template to use to generate the changelog'
  // })
  .parse()
;

var currentDate = moment();

var github = new GithubApi({
  version: '3.0.0'
, timeout: 10000
});

// github auth token
var token = null;

// ~/.config/changelog.json will store the token
var authOptions = {
  configName : 'changelog'
, scopes     : ['user', 'public_repo', 'repo']
};

Promise.promisifyAll(github.repos);
Promise.promisifyAll(github.issues);
Promise.promisifyAll(github.pullRequests);

var getTags = function(){
  var tagOpts = {
    user: opts.owner
  , repo: opts.repository
  };
  auth();
  return github.repos.getTagsAsync(tagOpts).map(function(ref){
    auth();
    return github.repos.getCommitAsync({
      user: tagOpts.user
    , repo: tagOpts.repo
    , sha: ref.commit.sha
    }).then(function(commit){
      return {
        name: ref.name
      , date: moment(commit.commit.committer.date)
      };
    });
  }).then(function(tags){
    return tags;
  });
};

var getPullRequests = function(){

  var issueOpts = {
    user: opts.owner
  , repo: opts.repository
  , state: 'closed'
  , sort: 'updated'
  , direction: 'desc'
  , per_page: 100
  , page: 1
  // , since: null // TODO: this is an improvement to save API calls
  };

  var getIssues = function(options){
    auth();
    return github.issues.repoIssuesAsync(options).then(function(issues){
      opts.verbose && console.log('issues pulled - ', issues.length);
      opts.verbose && console.log('issues page - ', options.page);
      return issues;
    });
  };

  return getIssues(issueOpts).then(function(issues){
    var linkHeader = linkParser(issues.meta.link)
    var totalPages = (linkHeader && linkHeader.last) ? linkHeader.last.page : 1;

    if (totalPages > issueOpts.page) {
      var allReqs = [];
      for(var i=issueOpts.page; i<totalPages; i++){
        var newOptions = _.clone(issueOpts, true);
        newOptions.page += i;
        allReqs.push(getIssues(newOptions));
      }
      return Promise.all(allReqs).reduce(function(issues, moreIssues){
        return issues.concat(moreIssues);
      }, issues);
    }
    return issues;
  }).map(function(issue){
    if (!issue.pull_request.html_url) return;

    auth();
    return github.pullRequests.getAsync({
      user: issueOpts.user
    , repo: issueOpts.repo
    , number: issue.number
    }).then(function(pr){
      if (pr.base.ref !== opts.branch) return;
      if (!pr.merged_at) return;
      return {
        title: pr.title
      , body: pr.body
      , number: pr.number
      , html_url: pr.html_url
      , 'merged_at': moment(pr.merged_at)
      , user: {login: (pr.user||0).login || null}
      };
    });
  }).reduce(function(scrubbed, pr){
    if (pr) scrubbed.push(pr);
    return scrubbed;
  }, [])
  .then(function(prs){
    return prs;
  });
};

// sortedTags must be an array of tag data sorted by tag date DESC
var tagPr = function(sortedTags, pr) {
  var current = null;
  for (var i=0, len=sortedTags.length; i < len; i++) {
    var tag = sortedTags[i];
    if (tag.date < pr.merged_at) break;
    current = tag;
  }
  if (!current) current = {name: opts.tagname, date: currentDate};
  return current;
};

var formatter = function(data) {
  var currentTagName = '';
  var output = "## Change Log\n";
  data.forEach(function(pr){
    if (pr.tag === null) {
      currentTagName = opts.tagname;
      output+= "\n### " + opts.tagname;
      output+= "\n";
    } else if (pr.tag.name != currentTagName) {
      currentTagName = pr.tag.name;
      output+= "\n### " + pr.tag.name
      output+= " (" + pr.tag.date.utc().format("YYYY/MM/DD HH:mm Z") + ")";
      output+= "\n";
    }

    output += "- [#" + pr.number + "](" + pr.html_url + ") " + pr.title
    if (pr.user.login) output += " (@" + pr.user.login + ")";
    if (opts.issuebody && pr.body && pr.body.trim()) output += "\n\n    >" + pr.body.trim().replace(/\n/ig, "\n    > ");
    output += "\n\n";
  });
  return output.trim();
};

var getGithubToken = function() {
  if (opts.token) return Promise.resolve({token: opts.token});
  if (opts.auth) return ghauth(authOptions);
  return Promise.resolve({});
};

var auth = function() {
  if (!token) return;
  github.authenticate({type: 'oauth', token: token});
};

var task = function() {
  getGithubToken()
    .then(function(authData){
      if (!authData.token) return;
      token = authData.token;
    })
    .then(function(){
      return Promise.all([getTags(), getPullRequests()])
    })
    .spread(function(tags, prs){
      allTags = _.sortBy(tags, 'date').reverse();
      return prs;
    })
    .map(function(pr){
      pr.tag = tagPr(allTags, pr);
      pr.tagDate = pr.tag.date;
      return pr;
    })
    .then(function(data){
      data = _.sortBy(data, 'tagDate').reverse();
      return data;
    })
    .then(function(data){
      fs.writeFileSync(opts.file, formatter(data));
    })
    .then(function(){
      process.exit(0);
    })
    .catch(function(error){
      console.error('error', error);
      console.error('stack', error.stack);
      process.exit(1);
    })
  ;
};

var done = function (error) {
  if (!error) process.exit(0);
  console.log(error);
  console.log(error.stack);
  process.exit(1);
};

var runner = function () {
  var d = domain.create();
  d.on('error', done);
  d.run(task);
};

runner();<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict';

const path = require('path');
const meow = require('meow');
const mdpdf = require('../');

const cli = meow(`
    Usage:
        $ mdpdf <source> [<destination>] [options]

    <source> must be a markdown file, with the extension '.md'.

    Examples:
        $ mdpdf README.md
        $ mdpdf README.md --style=styles.css --header=header.hbs --h-height=22mm
        $ mdpdf README.md --footer=footer.hbs --f-height=22mm --debug
        $ mdpdf README.md --border-left=30mm

    Options:
        --style=<filename>      A single css stylesheet you wish to apply to the PDF
        --header=<filename>     A HTML (.html) file to inject into the header of the PDF
        --h-height=<height>     The height of the header section
        --footer=<filename>     A HTML (.html) file to inject into the footer of the PDF
        --f-height=<height>     The height of the footer section
        --border-top=<size>     Top border (default: 20mm)
        --border-left=<size>    Left border (default: 20mm)
        --border-bottom=<size>  Bottom border (default: 20mm)
        --border-right=<size>   Right border (default: 20mm)
        --no-emoji              Disables emoji conversions
        --debug                 Save the generated html for debugging
        --help                  Display this menu
        --version               Display the application version

        Length parameters (<height> and <size>) require a unit. Valid units are mm, cm, in and px.
`, {
	alias: {
		s: 'style',
		h: 'header',
		f: 'footer',
		d: 'debug',
		v: 'version'
	}
});

function isMd(path) {
	if (!path) {
		return true;
	}
	const accepted = ['md'];
	const current = path.split('.').pop();
	if (accepted.indexOf(current) !== -1) {
		return true;
	}
	return false;
}

const source = cli.input[0];
if (!source || !isMd(source)) {
    // Invalid source, show help and exit
	cli.showHelp();
}

const destination = cli.input[1] || source.slice(0, source.indexOf('.md')) + '.pdf';
const debug = cli.flags.debug || false;
const style = cli.flags.style;
const header = cli.flags.header;
const headerHeight = cli.flags.hHeight;
const footer = cli.flags.footer;
const footerHeight = cli.flags.fHeight;
const borderTop = cli.flags.borderTop;
const borderLeft = cli.flags.borderLeft;
const borderBottom = cli.flags.borderBottom;
const borderRight = cli.flags.borderRight;

const options = {
	ghStyle: !style,
	defaultStyle: true,
	source: path.resolve(source),
	destination: path.resolve(destination),
	styles: style ? path.resolve(style) : null,
	header: header ? path.resolve(header) : null,
	footer: footer ? path.resolve(footer) : null,
	noEmoji: cli.flags.noEmoji || false,
	debug: debug ? source.slice(0, source.indexOf('.md')) + '.html' : null,
	pdf: {
		format: 'A4',
		quality: '100',
		base: path.join('file://', __dirname, '/assets/'),
		header: {
			height: headerHeight || null
		},
		footer: {
			height: footerHeight || null
		},
		border: {
			top: borderTop || '20mm',
			left: borderLeft || '20mm',
			bottom: borderBottom || '20mm',
			right: borderRight || '20mm'
		}
	}
};

mdpdf.convert(options).then(pdfPath => {
	console.log('✨ PDF created successfully at:', pdfPath);
}).catch(err => {
	console.error(err);
	process.exitCode = 1;
});
<commit_msg>Add `--border` option<commit_after>#!/usr/bin/env node
'use strict';

const path = require('path');
const meow = require('meow');
const mdpdf = require('../');

const cli = meow(`
    Usage:
        $ mdpdf <source> [<destination>] [options]

    <source> must be a markdown file, with the extension '.md'.

    Examples:
        $ mdpdf README.md
        $ mdpdf README.md --style=styles.css --header=header.hbs --h-height=22mm
        $ mdpdf README.md --footer=footer.hbs --f-height=22mm --debug
        $ mdpdf README.md --border-left=30mm

    Options:
        --style=<filename>      A single css stylesheet you wish to apply to the PDF
        --header=<filename>     A HTML (.html) file to inject into the header of the PDF
        --h-height=<height>     The height of the header section
        --footer=<filename>     A HTML (.html) file to inject into the footer of the PDF
        --f-height=<height>     The height of the footer section
        --border-top=<size>     Top border (default: 20mm)
        --border-left=<size>    Left border (default: 20mm)
        --border-bottom=<size>  Bottom border (default: 20mm)
        --border-right=<size>   Right border (default: 20mm)
        --no-emoji              Disables emoji conversions
        --debug                 Save the generated html for debugging
        --help                  Display this menu
        --version               Display the application version

        Length parameters (<height> and <size>) require a unit. Valid units are mm, cm, in and px.
`, {
	alias: {
		s: 'style',
		h: 'header',
		f: 'footer',
		d: 'debug',
		v: 'version'
	}
});

function isMd(path) {
	if (!path) {
		return true;
	}
	const accepted = ['md'];
	const current = path.split('.').pop();
	if (accepted.indexOf(current) !== -1) {
		return true;
	}
	return false;
}

const source = cli.input[0];
if (!source || !isMd(source)) {
    // Invalid source, show help and exit
	cli.showHelp();
}

const destination = cli.input[1] || source.slice(0, source.indexOf('.md')) + '.pdf';
const debug = cli.flags.debug || false;
const style = cli.flags.style;
const header = cli.flags.header;
const headerHeight = cli.flags.hHeight;
const footer = cli.flags.footer;
const footerHeight = cli.flags.fHeight;
const border = cli.flags.border || '20mm';
const borderTop = cli.flags.borderTop || border;
const borderLeft = cli.flags.borderLeft || border;
const borderBottom = cli.flags.borderBottom || border;
const borderRight = cli.flags.borderRight || border;

const options = {
	ghStyle: !style,
	defaultStyle: true,
	source: path.resolve(source),
	destination: path.resolve(destination),
	styles: style ? path.resolve(style) : null,
	header: header ? path.resolve(header) : null,
	footer: footer ? path.resolve(footer) : null,
	noEmoji: cli.flags.noEmoji || false,
	debug: debug ? source.slice(0, source.indexOf('.md')) + '.html' : null,
	pdf: {
		format: 'A4',
		quality: '100',
		base: path.join('file://', __dirname, '/assets/'),
		header: {
			height: headerHeight || null
		},
		footer: {
			height: footerHeight || null
		},
		border: {
			top: borderTop,
			left: borderLeft,
			bottom: borderBottom,
			right: borderRight
		}
	}
};

mdpdf.convert(options).then(pdfPath => {
	console.log('✨ PDF created successfully at:', pdfPath);
}).catch(err => {
	console.error(err);
	process.exitCode = 1;
});
<|endoftext|>


<commit_before>module.exports = (function() {

  var def = require('./def');
  var Handler = require('./Handler');

  var Jokes = def.type();

  Jokes.def_method(function match(msg) {
    return /[tT]ell a joke/.test(msg);
  });

  Jokes.def_method(function execute(msg) {
    if (this.match(msg.text))
      msg.reply('No.');
  });

  return Jokes;

}());
<commit_msg>Refactor to use method calls rather than direct def calls.<commit_after>module.exports = (function() {

  var def = require('./def');
  var Handler = require('./handler');

  var Jokes = def.type();

  Jokes.def_method(function match(msg) {
    return /[tT]ell a joke/.test(msg);
  });

  Jokes.def_method(function execute(msg) {
    if (this.match(msg.text))
      msg.reply('No.');
  });

  return Jokes;

}());
<|endoftext|>