<commit_before>const Booru = require('./dist')
const { BooruError, sites } = require('./dist')
// for ES6:
// import Booru, { search, BooruError, sites } from 'booru'

const argTags = process.argv.slice(3)
const site = process.argv[2] || 'sb'
const tags = process.argv[2] ? argTags : ['cat']

const searchUrl = Booru.forSite(site).getSearchUrl({ tags, limit: 1 })

console.log(`Searching with url: ${searchUrl}`)

// Search with promises
Booru.search(site, tags, { limit: 1, random: false })
  .then(posts => {
    if (posts.length === 0) {
      console.log('No images found.')
    }

    for (let post of posts) {
      console.log(post.fileUrl)
    }
  })
  .catch(err => {
    if (err instanceof BooruError) {
      // It's a custom error thrown by the package
      // Typically results from errors the boorus returns, eg. "too many tags"
      console.error(err)
    } else {
      // This means something pretty bad happened
      console.error(err)
    }
  })

// Search with async/await
async function booruSearch(site, tags, limit = 1, random = true) {
  const posts = await Booru.search(site, tags, {limit, random})

  return console.log(posts[0].fileUrl)
}

// Create an instance of a booru to use yourself
// This allows you to create a booru with certain credentials/settings and reuse it
// Internally, `Booru.search` just creates boorus and caches them
// Ex: `Booru.forSite('safebooru')`
async function booruClassSearch(site, tags, limit = 1, random = true) {
  const myBooru = Booru.forSite(site)
  const posts = await myBooru.search(tags, {limit, random})

  return console.log(posts[0].fileUrl)
}

// You can also check the sites and the options for each
//   console.log(Booru.sites)
// Or just the site URLs
//   console.log(Object.keys(sites))
<commit_msg>feat(example): formatting & log number of results found<commit_after>const Booru = require('./dist')
const { BooruError, sites } = require('./dist')
// for ES6:
// import Booru, { search, BooruError, sites } from 'booru'

const argTags = process.argv.slice(3)
const site = process.argv[2] || 'sb'
const tags = process.argv[2] ? argTags : ['cat']

const searchUrl = Booru.forSite(site).getSearchUrl({ tags, limit: 1 })

console.log(`Searching with url: ${searchUrl}`)

// Search with promises
Booru.search(site, tags, { limit: 1, random: false })
  .then((posts) => {
    if (posts.length === 0) {
      console.log('No images found.')
    }

    console.log(`Found ${posts.length} image${posts.length === 1 ? '' : 's'}.`)
    for (let post of posts) {
      console.log(post.fileUrl)
    }
  })
  .catch((err) => {
    if (err instanceof BooruError) {
      // It's a custom error thrown by the package
      // Typically results from errors the boorus returns, eg. "too many tags"
      console.error(err)
    } else {
      // This means something pretty bad happened
      console.error(err)
    }
  })

// Search with async/await
async function booruSearch(site, tags, limit = 1, random = true) {
  const posts = await Booru.search(site, tags, { limit, random })

  return console.log(posts[0].fileUrl)
}

// Create an instance of a booru to use yourself
// This allows you to create a booru with certain credentials/settings and reuse it
// Internally, `Booru.search` just creates boorus and caches them
// Ex: `Booru.forSite('safebooru')`
async function booruClassSearch(site, tags, limit = 1, random = true) {
  const myBooru = Booru.forSite(site)
  const posts = await myBooru.search(tags, { limit, random })

  return console.log(posts[0].fileUrl)
}

// You can also check the sites and the options for each
//   console.log(Booru.sites)
// Or just the site URLs
//   console.log(Object.keys(sites))
<|endoftext|>


<commit_before>const db = require('../models')
const models = db.sequelize.models
const Op = db.Sequelize.Op

module.exports = {
  async index (req, res, next) {
    if (req.query.count) {
      const count = req.query.count
      const response = await models.Community.findAll({
        offset: count - 10,
        limit: 10,
        order: [ [ 'createdAt', 'DESC' ]]
      })
      res.send(response)
    } else if (!req.query.count) {
    const response = await models.Community.findAll({
      limit: 10,
      order: [[ 'createdAt', 'DESC' ]]
    })
      res.send(response)
    } else {
      next()
    }
  }
}
<commit_msg>Fix: Fixed bug where the search was broken<commit_after>const db = require('../models')
const models = db.sequelize.models
const Op = db.Sequelize.Op

module.exports = {
  async index (req, res, next) {
    if (req.query.count) {
      const count = req.query.count
      const response = await models.Community.findAll({
        offset: count - 10,
        limit: 10,
        order: [ [ 'createdAt', 'DESC' ]]
      })
      res.send(response)
    } else if (Object.keys(req.query).length === 0 && req.query.constructor === Object) {
    const response = await models.Community.findAll({
      limit: 10,
      order: [[ 'createdAt', 'DESC' ]]
    })
      res.send(response)
    } else {
      next()
    }
  }
}
<|endoftext|>


<commit_before>#!/usr/bin/env node
/* eslint-disable no-console */
var _ = require("lodash");
var csv = require("fast-csv");
var async = require("async");
var yaml = require("js-yaml");
var hyperpotamus = require("./lib");
var verror = require("verror");
var Promise = require("bluebird");

var defaultConfigFile = require("path").join(require("os").homedir(), ".hyperpotamus", "config.json");
var args = require("./cli/args")(defaultConfigFile);

// Setup logging configuration
var logger = hyperpotamus.logging.logger("hyperpotamus.cli");
hyperpotamus.logging.set_level(args.verbose + 3); // Starts at Warning, adding --verbose flags bumps to INFO, DEBUG, or TRACE

if(args.calfinated) {
	process.env.CALFINATED = "true";
}
execute(args);

function execute(args) {
	var sessionDefaults = require("./cli/sessionDefaults.js")(args);
	var emitStreams = require("./cli/emitStreams.js")(args);

	var processor = new hyperpotamus.Processor({ safe: args.safe, emit: emitStreams });

	try {
		if (args.plugins) {
			processor.plugins.loadPlugins(args.plugins);
		}
	}
	catch (err) {
		dumpError("loading plugins", err);
	}

	var script;
	try {
		script = processor.loadFile(args.file);
	}
	catch (err) {
		dumpError("loading yaml script", err);
	}

	// Pre-normalize script if we run it in a loop and for display/logging
	try {
		script = processor.normalize(script);
		logger.trace("Normalized script:\n" + yaml.dump(script));
		if (args.normalize) {
			showNormalized(script);
			process.exit(0);
		}
	}
	catch (err) {
		dumpError("normalizing script", err);
	}

	// Worker queue to process requests with set concurrency
	var queue = async.queue(function (session, callback) {
		logger.debug("About to start session for " + JSON.stringify(session));
		var localSession = _.merge({}, sessionDefaults, session);
		processor.process(script, localSession, args.start)
			.tap(context => logger.info("Final session data is:\n" + JSON.stringify(context.session, null, 2)))
			.delay(0) // To ensure we can SIGINT even if no async work happens in the script
			.then(callback)
			.catch(_.partial(dumpError, "processing script"));
	}, args.concurrency);

	// Handler for graceful (or forced) shutdown
	var exiting = false;
	process.on("SIGINT", function () {
		if (!exiting) {
			exiting = true;
			queue.kill(); // Finish processing in-flight scripts, but stop any new ones from starting
			console.log("Gracefully shutting down from SIGINT (Press Ctrl-C again to exit immediately.)");
		}
		else {
			// Ok, tired of waiting, exit NOW!!
			process.exit(1);
		}
	});

	Promise.try(() => {
		if (args.init) {
			if (args.init === true) {
				args.init = "init";
			}

			// Run the script with the init steps
			return processor.process(script, sessionDefaults, args.init)
				.then(context => context.session)
				.catch(_.partial(dumpError, "processing init script"));
		}
		else {
			return sessionDefaults;
		}
	}).then(defaults => {
		sessionDefaults = defaults;
		if (args.csv) {
			logger.info("Loading data from csv file - " + args.csv);
			logger.info("Maximum concurrency level is " + args.concurrency);
			csv.fromPath(args.csv, { headers: true }).on("data", function (user) {
				if (!exiting) {
					queue.push(user);
					logger.trace("Queued user for processing " + JSON.stringify(user));
				}
			});
		}
		else if (args.loop) {
			var iterations = 0;
			// fill the queue up to max concurrency and whenever the queue is emptied (but still processing the current items), keep refilling until
			// we have finished. This prevents having to pre-queue a large (or potentially infinite) number of entries.
			queue.empty = () => {
				for (var i = 0; i < args.concurrency && (args.loop == true || iterations < args.loop); i++) {
					iterations++;
					queue.push({ "hyperpotamus.index": iterations });
					logger.trace("Queued user for processing #" + iterations);
				}
				if (args.loop === iterations) {
					// As soon as we have enough iterations, stop the refill. if args.loop == true it will never stop.
					queue.empty = _.noop;
				}
			};
			// Start filling
			queue.empty();
		}
		else {
			logger.trace("Processing script.");
			queue.push({});
		}
	});
}

function dumpError(message, err) {
	var cause = findCause(err);
	var dump = {
		name: cause.name,
		message: cause.message
	};
	var path = findDeepestInfoProperty(err, "path");
	if(!_.isNil(path)) {
		dump.path = path;
	}
	dump.help_link = `https://github.com/pmarkert/hyperpotamus/wiki/errors#${cause.name}`;
	logger.debug(`Error stack-trace:\n${verror.fullStack(err)}`);
	logger.info(`Error details:\n${yaml.dump(verror.info(err), { noRefs: true, lineWidth: process.stdout.columns, skipInvalid: true })}`);
	logger.error(`Error occurred while ${message}:\n${yaml.dump(dump, { noRefs: true, lineWidth: process.stdout.columns, skipInvalid: true })}`);
	process.exit(1);
}

function findCause(err) {
	var next;
	while((next = verror.cause(err))!=null) {
		if(next instanceof verror.VError) {
			err = next;
		}
		else {
			break;
		}
	}
	return err;
}

function findDeepestInfoProperty(err, property) {
	if(err instanceof verror.VError) {
		var result = findDeepestInfoProperty(verror.cause(err), property);
		return result || verror.info(err)[property];
	}
	return null;
}

function showNormalized(script) {
	console.log(`
Normalized JSON:
================
${JSON.stringify(script, null, 2)}

Normalized YAML:
================
${yaml.dump(script)}
`);
}
<commit_msg>Filtering the “hyperpotamus.*” session object from the default session output display.<commit_after>#!/usr/bin/env node
/* eslint-disable no-console */
var _ = require("lodash");
var csv = require("fast-csv");
var async = require("async");
var yaml = require("js-yaml");
var hyperpotamus = require("./lib");
var verror = require("verror");
var Promise = require("bluebird");

var defaultConfigFile = require("path").join(require("os").homedir(), ".hyperpotamus", "config.json");
var args = require("./cli/args")(defaultConfigFile);

// Setup logging configuration
var logger = hyperpotamus.logging.logger("hyperpotamus.cli");
hyperpotamus.logging.set_level(args.verbose + 3); // Starts at Warning, adding --verbose flags bumps to INFO, DEBUG, or TRACE

if(args.calfinated) {
	process.env.CALFINATED = "true";
}
execute(args);

function execute(args) {
	var sessionDefaults = require("./cli/sessionDefaults.js")(args);
	var emitStreams = require("./cli/emitStreams.js")(args);

	var processor = new hyperpotamus.Processor({ safe: args.safe, emit: emitStreams });

	try {
		if (args.plugins) {
			processor.plugins.loadPlugins(args.plugins);
		}
	}
	catch (err) {
		dumpError("loading plugins", err);
	}

	var script;
	try {
		script = processor.loadFile(args.file);
	}
	catch (err) {
		dumpError("loading yaml script", err);
	}

	// Pre-normalize script if we run it in a loop and for display/logging
	try {
		script = processor.normalize(script);
		logger.trace("Normalized script:\n" + yaml.dump(script));
		if (args.normalize) {
			showNormalized(script);
			process.exit(0);
		}
	}
	catch (err) {
		dumpError("normalizing script", err);
	}

	// Worker queue to process requests with set concurrency
	var queue = async.queue(function (session, callback) {
		logger.debug("About to start session for " + JSON.stringify(session));
		var localSession = _.merge({}, sessionDefaults, session);
		processor.process(script, localSession, args.start)
			.tap(context => logger.info("Final session data is:\n" + JSON.stringify(_.without(context.session, "hyperpotamus"), null, 2)))
			.delay(0) // To ensure we can SIGINT even if no async work happens in the script
			.then(callback)
			.catch(_.partial(dumpError, "processing script"));
	}, args.concurrency);

	// Handler for graceful (or forced) shutdown
	var exiting = false;
	process.on("SIGINT", function () {
		if (!exiting) {
			exiting = true;
			queue.kill(); // Finish processing in-flight scripts, but stop any new ones from starting
			console.log("Gracefully shutting down from SIGINT (Press Ctrl-C again to exit immediately.)");
		}
		else {
			// Ok, tired of waiting, exit NOW!!
			process.exit(1);
		}
	});

	Promise.try(() => {
		if (args.init) {
			if (args.init === true) {
				args.init = "init";
			}

			// Run the script with the init steps
			return processor.process(script, sessionDefaults, args.init)
				.then(context => context.session)
				.catch(_.partial(dumpError, "processing init script"));
		}
		else {
			return sessionDefaults;
		}
	}).then(defaults => {
		sessionDefaults = defaults;
		if (args.csv) {
			logger.info("Loading data from csv file - " + args.csv);
			logger.info("Maximum concurrency level is " + args.concurrency);
			csv.fromPath(args.csv, { headers: true }).on("data", function (user) {
				if (!exiting) {
					queue.push(user);
					logger.trace("Queued user for processing " + JSON.stringify(user));
				}
			});
		}
		else if (args.loop) {
			var iterations = 0;
			// fill the queue up to max concurrency and whenever the queue is emptied (but still processing the current items), keep refilling until
			// we have finished. This prevents having to pre-queue a large (or potentially infinite) number of entries.
			queue.empty = () => {
				for (var i = 0; i < args.concurrency && (args.loop == true || iterations < args.loop); i++) {
					iterations++;
					queue.push({ "hyperpotamus.index": iterations });
					logger.trace("Queued user for processing #" + iterations);
				}
				if (args.loop === iterations) {
					// As soon as we have enough iterations, stop the refill. if args.loop == true it will never stop.
					queue.empty = _.noop;
				}
			};
			// Start filling
			queue.empty();
		}
		else {
			logger.trace("Processing script.");
			queue.push({});
		}
	});
}

function dumpError(message, err) {
	var cause = findCause(err);
	var dump = {
		name: cause.name,
		message: cause.message
	};
	var path = findDeepestInfoProperty(err, "path");
	if(!_.isNil(path)) {
		dump.path = path;
	}
	dump.help_link = `https://github.com/pmarkert/hyperpotamus/wiki/errors#${cause.name}`;
	logger.debug(`Error stack-trace:\n${verror.fullStack(err)}`);
	logger.info(`Error details:\n${yaml.dump(verror.info(err), { noRefs: true, lineWidth: process.stdout.columns, skipInvalid: true })}`);
	logger.error(`Error occurred while ${message}:\n${yaml.dump(dump, { noRefs: true, lineWidth: process.stdout.columns, skipInvalid: true })}`);
	process.exit(1);
}

function findCause(err) {
	var next;
	while((next = verror.cause(err))!=null) {
		if(next instanceof verror.VError) {
			err = next;
		}
		else {
			break;
		}
	}
	return err;
}

function findDeepestInfoProperty(err, property) {
	if(err instanceof verror.VError) {
		var result = findDeepestInfoProperty(verror.cause(err), property);
		return result || verror.info(err)[property];
	}
	return null;
}

function showNormalized(script) {
	console.log(`
Normalized JSON:
================
${JSON.stringify(script, null, 2)}

Normalized YAML:
================
${yaml.dump(script)}
`);
}
<|endoftext|>


<commit_before>'use strict'

// DOM elements & variables
// =============================================================================

// Get menubar instance from main.js
const mb = require('electron').remote.getGlobal('sharedObject').mb
const path = require('path')
const Timer = require('tiny-timer')

const appContainer = document.querySelector('.js-app')
const startBtn = document.querySelector('.js-start-btn')
const stopBtn = document.querySelector('.js-stop-btn')
const slider = document.querySelector('.js-slider')

// Sounds
const soundWindup = new Audio(path.join(__dirname, '/wav/windup.wav'))
const soundClick = new Audio(path.join(__dirname, '/wav/click.wav'))
const soundDing = new Audio(path.join(__dirname, '/wav/ding.wav'))

let state = ''
let currentMinute = 0
const workMinutes = 25
const breakMinutes = 5

// Timer stuff
const timer = new Timer()

// Utilities
// =============================================================================
const minToMs = min => min * 60 * 1000

const msToMin = ms => ms / 60 / 1000

const getCurrentMinutes = () => state === 'breaking' ? breakMinutes : workMinutes

const getCurrentSliderWidth = () => state === 'breaking' ? 100 : 500

const playSound = sound => {
  sound.currentTime = 0
  sound.play()
}

// State handling
// =============================================================================

const setState = newState => {
  appContainer.classList.remove('is-stopped', 'is-working', 'is-breaking')
  appContainer.classList.add(`is-${newState}`)
  state = newState
}
setState('stopped')

const setCurrentMinute = ms => {
  currentMinute = Math.ceil(msToMin(ms))
  const breakSuffix = state === 'breaking' ? '-break' : ''
  mb.tray.setImage(`${__dirname}/img/icon-${currentMinute}${breakSuffix}-Template.png`)
}
setCurrentMinute(0)

// Event handlers
// =============================================================================

startBtn.addEventListener('click', () => {
  playSound(soundWindup)
  timer.start(minToMs(workMinutes))
  setState('working')
  slider.classList.add('is-resetting-work')
  setTimeout(() => slider.classList.remove('is-resetting-work'), 1000)
})

stopBtn.addEventListener('click', () => {
  playSound(soundClick)
  timer.stop()
  setState('stopped')
})

timer.on('tick', ms => {
  const minutes = getCurrentMinutes()
  const sliderWidth = getCurrentSliderWidth()
  slider.style.transform = 'translateX(-' + Math.ceil((sliderWidth * ms) / (minToMs(minutes))) + 'px)'
  setCurrentMinute(ms)
})

timer.on('done', () => {
  playSound(soundDing)
  setCurrentMinute(0)
  mb.showWindow()

  setTimeout(() => {
    playSound(soundWindup)
    if (state === 'working') {
      setState('breaking')
      timer.start(minToMs(breakMinutes))
      slider.classList.add('is-resetting-break')
      setTimeout(() => slider.classList.remove('is-resetting-break'), 1000)
    } else {
      setState('working')
      timer.start(minToMs(workMinutes))
      slider.classList.add('is-resetting-work')
      setTimeout(() => slider.classList.remove('is-resetting-work'), 1000)
    }
  }, 2000)
})
<commit_msg>Set current icon based on OS<commit_after>'use strict'

// DOM elements & variables
// =============================================================================

// Get menubar instance from main.js
const mb = require('electron').remote.getGlobal('sharedObject').mb
const path = require('path')
const Timer = require('tiny-timer')

const appContainer = document.querySelector('.js-app')
const startBtn = document.querySelector('.js-start-btn')
const stopBtn = document.querySelector('.js-stop-btn')
const slider = document.querySelector('.js-slider')

// Sounds
const soundWindup = new Audio(path.join(__dirname, '/wav/windup.wav'))
const soundClick = new Audio(path.join(__dirname, '/wav/click.wav'))
const soundDing = new Audio(path.join(__dirname, '/wav/ding.wav'))

let state = ''
let currentMinute = 0
const workMinutes = 25
const breakMinutes = 5

// Timer stuff
const timer = new Timer()

// Utilities
// =============================================================================
const minToMs = min => min * 60 * 1000

const msToMin = ms => ms / 60 / 1000

const getCurrentMinutes = () => state === 'breaking' ? breakMinutes : workMinutes

const getCurrentSliderWidth = () => state === 'breaking' ? 100 : 500

const playSound = sound => {
  sound.currentTime = 0
  sound.play()
}

// State handling
// =============================================================================

const setState = newState => {
  appContainer.classList.remove('is-stopped', 'is-working', 'is-breaking')
  appContainer.classList.add(`is-${newState}`)
  state = newState
}
setState('stopped')

const setIcon = currentMinute => {
  let file = ''
  const breakSuffix = state === 'breaking' ? '-break' : ''

  switch (process.platform) {
    case 'darwin':
      file = `${__dirname}/img/template/icon-${currentMinute}${breakSuffix}-Template.png`
      break
    case 'win32':
      file = `${__dirname}/img/ico/icon-${currentMinute}${breakSuffix}.ico`
      break
    default:
      file = `${__dirname}/img/png/icon-${currentMinute}${breakSuffix}.png`
  }
  mb.tray.setImage(file)
}

const setCurrentMinute = ms => {
  currentMinute = Math.ceil(msToMin(ms))
  setIcon(currentMinute)
}
setCurrentMinute(0)

// Event handlers
// =============================================================================

startBtn.addEventListener('click', () => {
  playSound(soundWindup)
  timer.start(minToMs(workMinutes))
  setState('working')
  slider.classList.add('is-resetting-work')
  setTimeout(() => slider.classList.remove('is-resetting-work'), 1000)
})

stopBtn.addEventListener('click', () => {
  playSound(soundClick)
  timer.stop()
  setState('stopped')
})

timer.on('tick', ms => {
  const minutes = getCurrentMinutes()
  const sliderWidth = getCurrentSliderWidth()
  slider.style.transform = 'translateX(-' + Math.ceil((sliderWidth * ms) / (minToMs(minutes))) + 'px)'
  setCurrentMinute(ms)
})

timer.on('done', () => {
  playSound(soundDing)
  setCurrentMinute(0)
  mb.showWindow()

  setTimeout(() => {
    playSound(soundWindup)
    if (state === 'working') {
      setState('breaking')
      timer.start(minToMs(breakMinutes))
      slider.classList.add('is-resetting-break')
      setTimeout(() => slider.classList.remove('is-resetting-break'), 1000)
    } else {
      setState('working')
      timer.start(minToMs(workMinutes))
      slider.classList.add('is-resetting-work')
      setTimeout(() => slider.classList.remove('is-resetting-work'), 1000)
    }
  }, 2000)
})
<|endoftext|>


<commit_before>/*eslint-disable*/
var domevent = tui.component.colorpicker.domevent;
describe('module:domevent', function() {
    beforeEach(function() {
        loadFixtures('domevent.html');
    });

    describe('on()', function() {
        it('can set context for event handlers', function() {
            var myObj = {};
            var btn = document.getElementById('btn1');
            var clickHandler = function() {
                expect(this).toBe(myObj);
            };

            domevent.on(btn, 'click', clickHandler, myObj);
            domevent.trigger(btn, 'click');
        });

        it('DOM엘리먼트에 이벤트 핸들러를 설정한다', function() {
            var btn = document.getElementById('btn1');
            var clickHandler = jasmine.createSpy('click');

            domevent.on(btn, 'click', clickHandler);
            domevent.trigger(btn, 'click');

            expect(clickHandler).toHaveBeenCalled();
        });

        it('이벤트명:핸들러 객체로도 설정할 수 있다', function() {
            var btn = document.getElementById('btn1');
            var handler = jasmine.createSpyObj('handler', ['click', 'mousedown']);

            domevent.on(btn, {
                'click': handler.click,
                'mousedown': handler.mousedown
            });

            domevent.trigger(btn, 'mousedown');
            domevent.trigger(btn, 'click');

            expect(handler.click).toHaveBeenCalled();
            expect(handler.mousedown).toHaveBeenCalled();
        });

        it('동일 파라미터로 두번 이상 바인딩 불가', function() {
            var btn = document.getElementById('btn1');

            var clickCount = 0;
            function clickHandler() {
                clickCount += 1;
            }

            domevent.on(btn, 'click', clickHandler);
            domevent.on(btn, 'click', clickHandler);

            domevent.trigger(btn, 'click');

            expect(clickCount).toBe(1);
        });

        it('브라우저마다 다른 마우스 휠 이벤트의 정규화 기능을 제공한다', function() {
            var div = document.getElementById('div1');

            function wheelHandler(eventData) {
                var wheel = domevent.getWheelDelta(eventData);
                expect(typeof wheel).toBe('number');
            }

            domevent.on(div, 'mousewheel', wheelHandler);

            domevent.trigger(div, 'mousewheel', domevent.mouseEvent('mousewheel', { detail: 120 }));
        });

    });

    describe('off()', function() {
        var btn,
            div,
            handler;

        beforeEach(function() {
            btn = document.getElementById('btn1');
            div = document.getElementById('div1');
            handler = jasmine.createSpyObj('handler', ['click', 'mousedown', 'mouseenter', 'mouseleave', 'mousewheel']);
        });

        it('DOM에 걸린 이벤트 핸들러를 제거한다', function() {
            domevent.on(btn, 'click', handler.click);

            domevent.off(btn, 'click', handler.click);
            domevent.trigger(btn, 'click');

            expect(handler.click).not.toHaveBeenCalled();
        });

        it('이벤트명:핸들러 객체를 사용해 한번에 여러 핸들러를 해제할 수 있다', function() {
            domevent.on(btn, {
                'click': handler.click,
                'mousedown': handler.mousedown,
                'mousewheel': handler.mousewheel
            });

            domevent.off(btn, {
                'click': handler.click,
                'mousedown': handler.mousedown,
                'mousewheel': handler.mousewheel
            });
            domevent.trigger(btn, 'mousedown');
            domevent.trigger(btn, 'click');
            domevent.trigger(btn, 'mousewheel', domevent.mouseEvent('mousewheel', { detail: 120 }));

            expect(handler.click).not.toHaveBeenCalled();
            expect(handler.mousedown).not.toHaveBeenCalled();
            expect(handler.mousewheel).not.toHaveBeenCalled();

            domevent.on(div, {
                'mouseenter': handler.mouseenter,
                'mouseleave': handler.mouseleave
            });

            domevent.off(div, {
                'mouseenter': handler.mouseenter,
                'mouseleave': handler.mouseleave
            });

            domevent.trigger(div, 'mouseenter');
            domevent.trigger(div, 'mouseleave');

            expect(handler.mouseenter).not.toHaveBeenCalled();
            expect(handler.mouseleave).not.toHaveBeenCalled();
        });

        it('등록된 이벤트가 전부 해제되면 관련 프로퍼티를 아예 제거한다.', function() {
            domevent.on(btn, {
                'click': handler.click,
                'mousedown': handler.mousedown,
                'mousewheel': handler.mousewheel
            });

            domevent.off(btn, {
                'click': handler.click,
                'mousedown': handler.mousedown,
                'mousewheel': handler.mousewheel
            });

            expect(btn._evt).toBeUndefined();
        });
    });

    describe('once()', function() {
        var btn;

        beforeEach(function() {
            btn = document.getElementById('btn1');
        });

        it('이벤트를 단발성으로 등록할 수 있다', function() {
            var clickCount = 0;
            function clickHandler() {
                clickCount += 1;
            }

            domevent.once(btn, 'click', clickHandler);

            domevent.trigger(btn, 'click');
            domevent.trigger(btn, 'click');

            expect(clickCount).toBe(1);
        });

        it('이벤트명:핸들러의 객체로 여러 핸들러를 단발성으로 한번에 등록할 수 있다', function() {
            var clickCount = 0,
                mouseDownCount = 0;

            function clickHandler() {
                clickCount += 1;
            }

            function mouseDownHandler() {
                mouseDownCount += 1;
            }

            domevent.once(btn, {
                'click': clickHandler,
                'mousedown': mouseDownHandler
            });

            domevent.trigger(btn, 'click');
            domevent.trigger(btn, 'click');
            domevent.trigger(btn, 'mousedown');
            domevent.trigger(btn, 'mousedown');

            expect(clickCount).toBe(1);
            expect(mouseDownCount).toBe(1);
        });
    });

    it('stopPropagation() 으로 이벤트의 버블링을 방지할 수 있다', function() {
        var wrap1 = document.getElementById('wrap1'),
            wrap2 = document.getElementById('wrap2');

        var handler = jasmine.createSpy('wrap1click');

        function wrap2click(e) {
            domevent.stopPropagation(e);
        }

        domevent.on(wrap1, 'click', handler);
        domevent.on(wrap2, 'click', wrap2click);

        domevent.trigger(wrap2, 'click');

        expect(handler).not.toHaveBeenCalled();
    });

    it('getMousePosition()에 컨테이너를 넘기면 특정 엘리먼트 기준의 마우스 위치를 쉽게 계산할 수 있다', function() {
        var wrap1 = document.getElementById('wrap1'),
            wrap2 = document.getElementById('wrap2');

        function clickHandler(clickEvent) {
            var point = domevent.getMousePosition(clickEvent),
                relativePoint = domevent.getMousePosition(clickEvent, wrap2);

            expect(point).toEqual([30, 30]);
            expect(relativePoint).toEqual([0, 10]);
        }

        domevent.on(wrap2, 'click', clickHandler);

        domevent.trigger(wrap2, 'click', domevent.mouseEvent('click', { clientX: 30, clientY: 30 }));
    });

});
<commit_msg>Fix domevent#off TC for IE7, 8 issue<commit_after>/*eslint-disable*/
var domevent = tui.component.colorpicker.domevent;
describe('module:domevent', function() {
    beforeEach(function() {
        loadFixtures('domevent.html');
    });

    describe('on()', function() {
        it('can set context for event handlers', function() {
            var myObj = {};
            var btn = document.getElementById('btn1');
            var clickHandler = function() {
                expect(this).toBe(myObj);
            };

            domevent.on(btn, 'click', clickHandler, myObj);
            domevent.trigger(btn, 'click');
        });

        it('DOM엘리먼트에 이벤트 핸들러를 설정한다', function() {
            var btn = document.getElementById('btn1');
            var clickHandler = jasmine.createSpy('click');

            domevent.on(btn, 'click', clickHandler);
            domevent.trigger(btn, 'click');

            expect(clickHandler).toHaveBeenCalled();
        });

        it('이벤트명:핸들러 객체로도 설정할 수 있다', function() {
            var btn = document.getElementById('btn1');
            var handler = jasmine.createSpyObj('handler', ['click', 'mousedown']);

            domevent.on(btn, {
                'click': handler.click,
                'mousedown': handler.mousedown
            });

            domevent.trigger(btn, 'mousedown');
            domevent.trigger(btn, 'click');

            expect(handler.click).toHaveBeenCalled();
            expect(handler.mousedown).toHaveBeenCalled();
        });

        it('동일 파라미터로 두번 이상 바인딩 불가', function() {
            var btn = document.getElementById('btn1');

            var clickCount = 0;
            function clickHandler() {
                clickCount += 1;
            }

            domevent.on(btn, 'click', clickHandler);
            domevent.on(btn, 'click', clickHandler);

            domevent.trigger(btn, 'click');

            expect(clickCount).toBe(1);
        });

        it('브라우저마다 다른 마우스 휠 이벤트의 정규화 기능을 제공한다', function() {
            var div = document.getElementById('div1');

            function wheelHandler(eventData) {
                var wheel = domevent.getWheelDelta(eventData);
                expect(typeof wheel).toBe('number');
            }

            domevent.on(div, 'mousewheel', wheelHandler);

            domevent.trigger(div, 'mousewheel', domevent.mouseEvent('mousewheel', { detail: 120 }));
        });

    });

    describe('off()', function() {
        var btn,
            div,
            handler;

        beforeEach(function() {
            btn = document.getElementById('btn1');
            div = document.getElementById('div1');
            handler = jasmine.createSpyObj('handler', ['click', 'mousedown', 'mouseenter', 'mouseleave', 'mousewheel']);
        });

        it('DOM에 걸린 이벤트 핸들러를 제거한다', function() {
            domevent.on(btn, 'click', handler.click);

            domevent.off(btn, 'click', handler.click);
            domevent.trigger(btn, 'click');

            expect(handler.click).not.toHaveBeenCalled();
        });

        it('이벤트명:핸들러 객체를 사용해 한번에 여러 핸들러를 해제할 수 있다', function() {
            domevent.on(btn, {
                'click': handler.click,
                'mousedown': handler.mousedown,
                'mousewheel': handler.mousewheel
            });

            domevent.off(btn, {
                'click': handler.click,
                'mousedown': handler.mousedown,
                'mousewheel': handler.mousewheel
            });
            domevent.trigger(btn, 'mousedown');
            domevent.trigger(btn, 'click');
            domevent.trigger(btn, 'mousewheel', domevent.mouseEvent('mousewheel', { detail: 120 }));

            expect(handler.click).not.toHaveBeenCalled();
            expect(handler.mousedown).not.toHaveBeenCalled();
            expect(handler.mousewheel).not.toHaveBeenCalled();

            domevent.on(div, {
                'mouseenter': handler.mouseenter,
                'mouseleave': handler.mouseleave
            });

            domevent.off(div, {
                'mouseenter': handler.mouseenter,
                'mouseleave': handler.mouseleave
            });

            domevent.trigger(div, 'mouseenter');
            domevent.trigger(div, 'mouseleave');

            expect(handler.mouseenter).not.toHaveBeenCalled();
            expect(handler.mouseleave).not.toHaveBeenCalled();
        });

        it('등록된 이벤트가 전부 해제되면 관련 프로퍼티를 아예 제거한다.', function() {
            domevent.on(btn, {
                'click': handler.click,
                'mousedown': handler.mousedown,
                'mousewheel': handler.mousewheel
            });

            domevent.off(btn, {
                'click': handler.click,
                'mousedown': handler.mousedown,
                'mousewheel': handler.mousewheel
            });

            expect(btn._evt).toBeFalsy();
        });
    });

    describe('once()', function() {
        var btn;

        beforeEach(function() {
            btn = document.getElementById('btn1');
        });

        it('이벤트를 단발성으로 등록할 수 있다', function() {
            var clickCount = 0;
            function clickHandler() {
                clickCount += 1;
            }

            domevent.once(btn, 'click', clickHandler);

            domevent.trigger(btn, 'click');
            domevent.trigger(btn, 'click');

            expect(clickCount).toBe(1);
        });

        it('이벤트명:핸들러의 객체로 여러 핸들러를 단발성으로 한번에 등록할 수 있다', function() {
            var clickCount = 0,
                mouseDownCount = 0;

            function clickHandler() {
                clickCount += 1;
            }

            function mouseDownHandler() {
                mouseDownCount += 1;
            }

            domevent.once(btn, {
                'click': clickHandler,
                'mousedown': mouseDownHandler
            });

            domevent.trigger(btn, 'click');
            domevent.trigger(btn, 'click');
            domevent.trigger(btn, 'mousedown');
            domevent.trigger(btn, 'mousedown');

            expect(clickCount).toBe(1);
            expect(mouseDownCount).toBe(1);
        });
    });

    it('stopPropagation() 으로 이벤트의 버블링을 방지할 수 있다', function() {
        var wrap1 = document.getElementById('wrap1'),
            wrap2 = document.getElementById('wrap2');

        var handler = jasmine.createSpy('wrap1click');

        function wrap2click(e) {
            domevent.stopPropagation(e);
        }

        domevent.on(wrap1, 'click', handler);
        domevent.on(wrap2, 'click', wrap2click);

        domevent.trigger(wrap2, 'click');

        expect(handler).not.toHaveBeenCalled();
    });

    it('getMousePosition()에 컨테이너를 넘기면 특정 엘리먼트 기준의 마우스 위치를 쉽게 계산할 수 있다', function() {
        var wrap1 = document.getElementById('wrap1'),
            wrap2 = document.getElementById('wrap2');

        function clickHandler(clickEvent) {
            var point = domevent.getMousePosition(clickEvent),
                relativePoint = domevent.getMousePosition(clickEvent, wrap2);

            expect(point).toEqual([30, 30]);
            expect(relativePoint).toEqual([0, 10]);
        }

        domevent.on(wrap2, 'click', clickHandler);

        domevent.trigger(wrap2, 'click', domevent.mouseEvent('click', { clientX: 30, clientY: 30 }));
    });

});
<|endoftext|>


<commit_before>import {DOM_CHANGED_EVENT} from '../classes/fef-dom-observer';

const COMPONENT_LOADED = 'fef.component.image.slider.loaded';

// Self loading on document.ready and updates if dom change event fired
$(window).on(DOM_CHANGED_EVENT, (e) => {
    $('.image-slider').each((index, element) => {
        new FefImageSlider($(element));
    });
});

export class FefImageSlider {

    constructor($element) {

        let currentPosition = 50;

        this.bindMotionEvents($element);
        this.bindWindowResizeEvents($element);

        this.bindMoveLeftClick(currentPosition, $element);
        this.bindMoveRightClick(currentPosition, $element);

        $(window).trigger(COMPONENT_LOADED, { 'component': this, 'element': $element });
        $element.addClass('ready');
    }

    /**
     * @param $element
     */
    bindMotionEvents($element) {

        $element.on('mousemove touchstart touchmove', (event) => {
            let compBoxLeft = $element.offset().left;

            if (event.type === 'touchstart' || event.type === 'touchmove') {
                event = event.originalEvent.touches[0];
            }

            this.moveSlider(event, compBoxLeft, $element);
        });
    }

    /**
     * @param $element
     */
    bindWindowResizeEvents($element) {
        $(window).resize( (event) => {
            let compBoxLeft = $element.offset().left;
            this.moveSlider(event, compBoxLeft, $element);
        });
    }

    /**
     * @param currentPosition
     * @param $element
     */
    bindMoveLeftClick(currentPosition, $element) {
        $element.parent().find('.image-slider__move-left').on('click', (event) => {
            event.preventDefault();
            let newPosition = currentPosition > 0 ? currentPosition - 10 : currentPosition;
            currentPosition = newPosition;
            this.moveSlider(event, '', $element, newPosition);
        });
    }

    /**
     * @param currentPosition
     * @param $element
     */
    bindMoveRightClick(currentPosition, $element) {
        $element.parent().find('.image-slider__move-right').on('click', (event) => {
            event.preventDefault();
            let newPosition = currentPosition < 100 ? currentPosition + 10 : currentPosition;
            currentPosition = newPosition;
            moveSlider(event, '', $element, newPosition);
        });
    }

    /**
     * @param event
     * @param compBoxLeft
     * @param $element
     * @param newPosition
     */
    moveSlider(event, compBoxLeft, $element, newPosition) {
        let position = typeof newPosition == 'undefined' ? ((event.pageX - compBoxLeft) / $element.outerWidth()) * 100 : newPosition;
        let $wrapper = $element.find('.image-slider__cover-wrapper'),
            $coverImage = $element.find('.image-slider__image--cover'),
            $sliderElementLeft = $element.find('.image-slider__slider--left'),
            $sliderElementRight = $element.find('.image-slider__slider--right');

        if (position >= 0 && position <= 100) {
            $wrapper.css('width', position + '%');
            $coverImage.width(((100 / position) * 100) + '%');
            $sliderElementLeft.css({'left': position + '%'});
            $sliderElementRight.css({'left': position + '%'});
        }
    }

}
<commit_msg>SRFCMSAL-78 - Export const<commit_after>import {DOM_CHANGED_EVENT} from '../classes/fef-dom-observer';

export const COMPONENT_LOADED = 'fef.component.image.slider.loaded';

// Self loading on document.ready and updates if dom change event fired
$(window).on(DOM_CHANGED_EVENT, (e) => {
    $('.image-slider').each((index, element) => {
        new FefImageSlider($(element));
    });
});

export class FefImageSlider {

    constructor($element) {

        let currentPosition = 50;

        this.bindMotionEvents($element);
        this.bindWindowResizeEvents($element);

        this.bindMoveLeftClick(currentPosition, $element);
        this.bindMoveRightClick(currentPosition, $element);

        $(window).trigger(COMPONENT_LOADED, { 'component': this, 'element': $element });
        $element.addClass('ready');
    }

    /**
     * @param $element
     */
    bindMotionEvents($element) {

        $element.on('mousemove touchstart touchmove', (event) => {
            let compBoxLeft = $element.offset().left;

            if (event.type === 'touchstart' || event.type === 'touchmove') {
                event = event.originalEvent.touches[0];
            }

            this.moveSlider(event, compBoxLeft, $element);
        });
    }

    /**
     * @param $element
     */
    bindWindowResizeEvents($element) {
        $(window).resize( (event) => {
            let compBoxLeft = $element.offset().left;
            this.moveSlider(event, compBoxLeft, $element);
        });
    }

    /**
     * @param currentPosition
     * @param $element
     */
    bindMoveLeftClick(currentPosition, $element) {
        $element.parent().find('.image-slider__move-left').on('click', (event) => {
            event.preventDefault();
            let newPosition = currentPosition > 0 ? currentPosition - 10 : currentPosition;
            currentPosition = newPosition;
            this.moveSlider(event, '', $element, newPosition);
        });
    }

    /**
     * @param currentPosition
     * @param $element
     */
    bindMoveRightClick(currentPosition, $element) {
        $element.parent().find('.image-slider__move-right').on('click', (event) => {
            event.preventDefault();
            let newPosition = currentPosition < 100 ? currentPosition + 10 : currentPosition;
            currentPosition = newPosition;
            moveSlider(event, '', $element, newPosition);
        });
    }

    /**
     * @param event
     * @param compBoxLeft
     * @param $element
     * @param newPosition
     */
    moveSlider(event, compBoxLeft, $element, newPosition) {
        let position = typeof newPosition == 'undefined' ? ((event.pageX - compBoxLeft) / $element.outerWidth()) * 100 : newPosition;
        let $wrapper = $element.find('.image-slider__cover-wrapper'),
            $coverImage = $element.find('.image-slider__image--cover'),
            $sliderElementLeft = $element.find('.image-slider__slider--left'),
            $sliderElementRight = $element.find('.image-slider__slider--right');

        if (position >= 0 && position <= 100) {
            $wrapper.css('width', position + '%');
            $coverImage.width(((100 / position) * 100) + '%');
            $sliderElementLeft.css({'left': position + '%'});
            $sliderElementRight.css({'left': position + '%'});
        }
    }

}
<|endoftext|>


<commit_before>/**
 * @license Copyright 2017 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */
/**
  * @fileoverview Gathers all images used on the page with their src, size,
  *   and attribute information. Executes script in the context of the page.
  */
'use strict';

const Gatherer = require('./gatherer');
const pageFunctions = require('../../lib/page-functions.js');
const Driver = require('../driver.js'); // eslint-disable-line no-unused-vars

/* global window, getElementsInDocument, Image */

/** @return {Array<LH.Artifacts.SingleImageUsage>} */
/* istanbul ignore next */
function collectImageElementInfo() {
  /** @param {Element} element */
  function getClientRect(element) {
    const clientRect = element.getBoundingClientRect();
    return {
      // manually copy the properties because ClientRect does not JSONify
      top: clientRect.top,
      bottom: clientRect.bottom,
      left: clientRect.left,
      right: clientRect.right,
    };
  }

  /** @type {Array<Element>} */
  // @ts-ignore - added by getElementsInDocumentFnString
  const allElements = getElementsInDocument();
  const allImageElements = /** @type {Array<HTMLImageElement>} */ (allElements.filter(element => {
    return element.localName === 'img';
  }));

  /** @type {Array<LH.Artifacts.SingleImageUsage>} */
  const htmlImages = allImageElements.map(element => {
    const computedStyle = window.getComputedStyle(element);
    return {
      // currentSrc used over src to get the url as determined by the browser
      // after taking into account srcset/media/sizes/etc.
      src: element.currentSrc,
      width: element.width,
      height: element.height,
      clientWidth: element.clientWidth,
      clientHeight: element.clientHeight,
      clientRect: getClientRect(element),
      naturalWidth: element.naturalWidth,
      naturalHeight: element.naturalHeight,
      isCss: false,
      isPicture: !!element.parentElement && element.parentElement.tagName === 'PICTURE',
      usesObjectFit: computedStyle.getPropertyValue('object-fit') === 'cover'
      || computedStyle.getPropertyValue('object-fit') === 'contain',
    };
  });

  // Chrome normalizes background image style from getComputedStyle to be an absolute URL in quotes.
  // Only match basic background-image: url("http://host/image.jpeg") declarations
  const CSS_URL_REGEX = /^url\("([^"]+)"\)$/;
  // Only find images that aren't specifically scaled
  const CSS_SIZE_REGEX = /(auto|contain|cover)/;

  const cssImages = allElements.reduce((images, element) => {
    const style = window.getComputedStyle(element);
    if (!style.backgroundImage || !CSS_URL_REGEX.test(style.backgroundImage) ||
        !style.backgroundSize || !CSS_SIZE_REGEX.test(style.backgroundSize)) {
      return images;
    }

    const imageMatch = style.backgroundImage.match(CSS_URL_REGEX);
    // @ts-ignore test() above ensures that there is a match.
    const url = imageMatch[1];

    // Heuristic to filter out sprite sheets
    const differentImages = images.filter(image => image.src !== url);
    if (images.length - differentImages.length > 2) {
      return differentImages;
    }

    images.push({
      src: url,
      clientWidth: element.clientWidth,
      clientHeight: element.clientHeight,
      clientRect: getClientRect(element),
      // CSS Images do not expose natural size, we'll determine the size later
      naturalWidth: Number.MAX_VALUE,
      naturalHeight: Number.MAX_VALUE,
      isCss: true,
      isPicture: false,
      usesObjectFit: false,
    });

    return images;
  }, /** @type {Array<LH.Artifacts.SingleImageUsage>} */ ([]));

  return htmlImages.concat(cssImages);
}

/**
 * @param {string} url
 * @return {Promise<{naturalWidth: number, naturalHeight: number}>}
 */
/* istanbul ignore next */
function determineNaturalSize(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.addEventListener('error', _ => reject(new Error('determineNaturalSize failed img load')));
    img.addEventListener('load', () => {
      resolve({
        naturalWidth: img.naturalWidth,
        naturalHeight: img.naturalHeight,
      });
    });

    img.src = url;
  });
}

class ImageUsage extends Gatherer {
  /**
   * @param {Driver} driver
   * @param {LH.Artifacts.SingleImageUsage} element
   * @return {Promise<LH.Artifacts.SingleImageUsage>}
   */
  async fetchElementWithSizeInformation(driver, element) {
    const url = JSON.stringify(element.src);
    try {
      /** @type {{naturalWidth: number, naturalHeight: number}} */
      const size = await driver.evaluateAsync(`(${determineNaturalSize.toString()})(${url})`);
      return Object.assign(element, size);
    } catch (_) {
      // determineNaturalSize fails on invalid images, which we treat as non-visible
      return Object.assign(element, {naturalWidth: 0, naturalHeight: 0});
    }
  }

  /**
   * @param {LH.Gatherer.PassContext} passContext
   * @param {LH.Gatherer.LoadData} loadData
   * @return {Promise<LH.Artifacts['ImageUsage']>}
   */
  async afterPass(passContext, loadData) {
    const driver = passContext.driver;
    const indexedNetworkRecords = loadData.networkRecords.reduce((map, record) => {
      if (/^image/.test(record.mimeType) && record.finished) {
        map[record.url] = {
          url: record.url,
          resourceSize: Math.min(record.resourceSize || 0, record.transferSize),
          startTime: record.startTime,
          endTime: record.endTime,
          responseReceivedTime: record.responseReceivedTime,
          mimeType: record.mimeType,
        };
      }

      return map;
    }, /** @type {Object<string, LH.Artifacts.SingleImageUsage['networkRecord']>} */ ({}));

    const expression = `(function() {
      ${pageFunctions.getElementsInDocumentString}; // define function on page
      return (${collectImageElementInfo.toString()})();
    })()`;

    /** @type {Array<LH.Artifacts.SingleImageUsage>} */
    const elements = await driver.evaluateAsync(expression);

    const imageUsage = [];
    for (let element of elements) {
      // link up the image with its network record
      element.networkRecord = indexedNetworkRecords[element.src];

      // Images within `picture` behave strangely and natural size information isn't accurate,
      // CSS images have no natural size information at all. Try to get the actual size if we can.
      // Additional fetch is expensive; don't bother if we don't have a networkRecord for the image.
      if ((element.isPicture || element.isCss) && element.networkRecord) {
        element = await this.fetchElementWithSizeInformation(driver, element);
      }

      imageUsage.push(element);
    }

    return imageUsage;
  }
}

module.exports = ImageUsage;
<commit_msg>core(gather): fix usesObjectFit property for ImageElement (#6761)<commit_after>/**
 * @license Copyright 2017 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */
/**
  * @fileoverview Gathers all images used on the page with their src, size,
  *   and attribute information. Executes script in the context of the page.
  */
'use strict';

const Gatherer = require('./gatherer');
const pageFunctions = require('../../lib/page-functions.js');
const Driver = require('../driver.js'); // eslint-disable-line no-unused-vars

/* global window, getElementsInDocument, Image */

/** @return {Array<LH.Artifacts.SingleImageUsage>} */
/* istanbul ignore next */
function collectImageElementInfo() {
  /** @param {Element} element */
  function getClientRect(element) {
    const clientRect = element.getBoundingClientRect();
    return {
      // manually copy the properties because ClientRect does not JSONify
      top: clientRect.top,
      bottom: clientRect.bottom,
      left: clientRect.left,
      right: clientRect.right,
    };
  }

  /** @type {Array<Element>} */
  // @ts-ignore - added by getElementsInDocumentFnString
  const allElements = getElementsInDocument();
  const allImageElements = /** @type {Array<HTMLImageElement>} */ (allElements.filter(element => {
    return element.localName === 'img';
  }));

  /** @type {Array<LH.Artifacts.SingleImageUsage>} */
  const htmlImages = allImageElements.map(element => {
    const computedStyle = window.getComputedStyle(element);
    return {
      // currentSrc used over src to get the url as determined by the browser
      // after taking into account srcset/media/sizes/etc.
      src: element.currentSrc,
      width: element.width,
      height: element.height,
      clientWidth: element.clientWidth,
      clientHeight: element.clientHeight,
      clientRect: getClientRect(element),
      naturalWidth: element.naturalWidth,
      naturalHeight: element.naturalHeight,
      isCss: false,
      isPicture: !!element.parentElement && element.parentElement.tagName === 'PICTURE',
      usesObjectFit: ['cover', 'contain', 'scale-down', 'none'].includes(
        computedStyle.getPropertyValue('object-fit')
      ),
    };
  });

  // Chrome normalizes background image style from getComputedStyle to be an absolute URL in quotes.
  // Only match basic background-image: url("http://host/image.jpeg") declarations
  const CSS_URL_REGEX = /^url\("([^"]+)"\)$/;
  // Only find images that aren't specifically scaled
  const CSS_SIZE_REGEX = /(auto|contain|cover)/;

  const cssImages = allElements.reduce((images, element) => {
    const style = window.getComputedStyle(element);
    if (!style.backgroundImage || !CSS_URL_REGEX.test(style.backgroundImage) ||
        !style.backgroundSize || !CSS_SIZE_REGEX.test(style.backgroundSize)) {
      return images;
    }

    const imageMatch = style.backgroundImage.match(CSS_URL_REGEX);
    // @ts-ignore test() above ensures that there is a match.
    const url = imageMatch[1];

    // Heuristic to filter out sprite sheets
    const differentImages = images.filter(image => image.src !== url);
    if (images.length - differentImages.length > 2) {
      return differentImages;
    }

    images.push({
      src: url,
      clientWidth: element.clientWidth,
      clientHeight: element.clientHeight,
      clientRect: getClientRect(element),
      // CSS Images do not expose natural size, we'll determine the size later
      naturalWidth: Number.MAX_VALUE,
      naturalHeight: Number.MAX_VALUE,
      isCss: true,
      isPicture: false,
      usesObjectFit: false,
    });

    return images;
  }, /** @type {Array<LH.Artifacts.SingleImageUsage>} */ ([]));

  return htmlImages.concat(cssImages);
}

/**
 * @param {string} url
 * @return {Promise<{naturalWidth: number, naturalHeight: number}>}
 */
/* istanbul ignore next */
function determineNaturalSize(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.addEventListener('error', _ => reject(new Error('determineNaturalSize failed img load')));
    img.addEventListener('load', () => {
      resolve({
        naturalWidth: img.naturalWidth,
        naturalHeight: img.naturalHeight,
      });
    });

    img.src = url;
  });
}

class ImageUsage extends Gatherer {
  /**
   * @param {Driver} driver
   * @param {LH.Artifacts.SingleImageUsage} element
   * @return {Promise<LH.Artifacts.SingleImageUsage>}
   */
  async fetchElementWithSizeInformation(driver, element) {
    const url = JSON.stringify(element.src);
    try {
      /** @type {{naturalWidth: number, naturalHeight: number}} */
      const size = await driver.evaluateAsync(`(${determineNaturalSize.toString()})(${url})`);
      return Object.assign(element, size);
    } catch (_) {
      // determineNaturalSize fails on invalid images, which we treat as non-visible
      return Object.assign(element, {naturalWidth: 0, naturalHeight: 0});
    }
  }

  /**
   * @param {LH.Gatherer.PassContext} passContext
   * @param {LH.Gatherer.LoadData} loadData
   * @return {Promise<LH.Artifacts['ImageUsage']>}
   */
  async afterPass(passContext, loadData) {
    const driver = passContext.driver;
    const indexedNetworkRecords = loadData.networkRecords.reduce((map, record) => {
      if (/^image/.test(record.mimeType) && record.finished) {
        map[record.url] = {
          url: record.url,
          resourceSize: Math.min(record.resourceSize || 0, record.transferSize),
          startTime: record.startTime,
          endTime: record.endTime,
          responseReceivedTime: record.responseReceivedTime,
          mimeType: record.mimeType,
        };
      }

      return map;
    }, /** @type {Object<string, LH.Artifacts.SingleImageUsage['networkRecord']>} */ ({}));

    const expression = `(function() {
      ${pageFunctions.getElementsInDocumentString}; // define function on page
      return (${collectImageElementInfo.toString()})();
    })()`;

    /** @type {Array<LH.Artifacts.SingleImageUsage>} */
    const elements = await driver.evaluateAsync(expression);

    const imageUsage = [];
    for (let element of elements) {
      // link up the image with its network record
      element.networkRecord = indexedNetworkRecords[element.src];

      // Images within `picture` behave strangely and natural size information isn't accurate,
      // CSS images have no natural size information at all. Try to get the actual size if we can.
      // Additional fetch is expensive; don't bother if we don't have a networkRecord for the image.
      if ((element.isPicture || element.isCss) && element.networkRecord) {
        element = await this.fetchElementWithSizeInformation(driver, element);
      }

      imageUsage.push(element);
    }

    return imageUsage;
  }
}

module.exports = ImageUsage;
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { create } from 'jss';
import { makeStyles, useTheme, jssPreset, StylesProvider } from '@material-ui/core/styles';
import rtl from 'jss-rtl';
import { useSelector } from 'react-redux';
import DemoErrorBoundary from 'docs/src/modules/components/DemoErrorBoundary';

function FramedDemo(props) {
  const { children, document } = props;

  const theme = useTheme();
  React.useEffect(() => {
    document.body.dir = theme.direction;
  }, [document, theme.direction]);

  const { jss, sheetsManager } = React.useMemo(() => {
    return {
      jss: create({
        plugins: [...jssPreset().plugins, rtl()],
        insertionPoint: document.head,
      }),
      sheetsManager: new Map(),
    };
  }, [document]);

  const getWindow = React.useCallback(() => document.defaultView, [document]);

  return (
    <StylesProvider jss={jss} sheetsManager={sheetsManager}>
      {React.cloneElement(children, {
        window: getWindow,
      })}
    </StylesProvider>
  );
}
FramedDemo.propTypes = {
  children: PropTypes.node,
  document: PropTypes.object.isRequired,
};

const useStyles = makeStyles(
  (theme) => ({
    frame: {
      backgroundColor: theme.palette.background.default,
      flexGrow: 1,
      height: 400,
      border: 'none',
      boxShadow: theme.shadows[1],
    },
  }),
  { name: 'DemoFrame' },
);

function DemoFrame(props) {
  const { children, title, ...other } = props;
  const classes = useStyles();
  /**
   * @type {import('react').Ref<HTMLIFrameElement>}
   */
  const frameRef = React.useRef(null);

  const [mounted, setMounted] = React.useState(false);
  React.useEffect(() => {
    setMounted(true);
  }, []);

  const document = frameRef.current?.contentDocument;

  return (
    <React.Fragment>
      <iframe className={classes.frame} ref={frameRef} title={title} {...other} />
      {mounted
        ? ReactDOM.createPortal(
            <FramedDemo document={document}>{children}</FramedDemo>,
            document.body,
          )
        : null}
    </React.Fragment>
  );
}

DemoFrame.propTypes = {
  children: PropTypes.node.isRequired,
  title: PropTypes.string.isRequired,
};

/**
 * Isolates the demo component as best as possible. Additional props are spread
 * to an `iframe` if `iframe={true}`.
 */
function DemoSandboxed(props) {
  const { component: Component, iframe, name, onResetDemoClick, ...other } = props;
  const Sandbox = iframe ? DemoFrame : React.Fragment;
  const sandboxProps = iframe ? { title: `${name} demo`, ...other } : {};

  const t = useSelector((state) => state.options.t);

  return (
    <DemoErrorBoundary onResetDemoClick={onResetDemoClick} t={t}>
      <Sandbox {...sandboxProps}>
        <Component />
      </Sandbox>
    </DemoErrorBoundary>
  );
}

DemoSandboxed.propTypes = {
  component: PropTypes.elementType.isRequired,
  iframe: PropTypes.bool,
  name: PropTypes.string.isRequired,
  onResetDemoClick: PropTypes.func.isRequired,
};

export default React.memo(DemoSandboxed);
<commit_msg>[docs] Fix dropped iframe content in firefox (#20686)<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { create } from 'jss';
import { makeStyles, useTheme, jssPreset, StylesProvider } from '@material-ui/core/styles';
import rtl from 'jss-rtl';
import { useSelector } from 'react-redux';
import DemoErrorBoundary from 'docs/src/modules/components/DemoErrorBoundary';

function FramedDemo(props) {
  const { children, document } = props;

  const theme = useTheme();
  React.useEffect(() => {
    document.body.dir = theme.direction;
  }, [document, theme.direction]);

  const { jss, sheetsManager } = React.useMemo(() => {
    return {
      jss: create({
        plugins: [...jssPreset().plugins, rtl()],
        insertionPoint: document.head,
      }),
      sheetsManager: new Map(),
    };
  }, [document]);

  const getWindow = React.useCallback(() => document.defaultView, [document]);

  return (
    <StylesProvider jss={jss} sheetsManager={sheetsManager}>
      {React.cloneElement(children, {
        window: getWindow,
      })}
    </StylesProvider>
  );
}
FramedDemo.propTypes = {
  children: PropTypes.node,
  document: PropTypes.object.isRequired,
};

const useStyles = makeStyles(
  (theme) => ({
    frame: {
      backgroundColor: theme.palette.background.default,
      flexGrow: 1,
      height: 400,
      border: 'none',
      boxShadow: theme.shadows[1],
    },
  }),
  { name: 'DemoFrame' },
);

function DemoFrame(props) {
  const { children, title, ...other } = props;
  const classes = useStyles();
  /**
   * @type {import('react').Ref<HTMLIFrameElement>}
   */
  const frameRef = React.useRef(null);

  // If we portal content into the iframe before the load event then that content
  // is dropped in firefox.
  const [iframeLoaded, onLoad] = React.useReducer(() => true, false);

  React.useEffect(() => {
    const document = frameRef.current.contentDocument;
    // When we hydrate the iframe then the load event is already dispatched
    // once the iframe markup is parsed (maybe later but the important part is
    // that it happens before React can attach event listeners).
    // We need to check the readyState of the document once the iframe is mounted
    // and "replay" the missed load event.
    // See https://github.com/facebook/react/pull/13862 for ongoing effort in React
    // (though not with iframes in mind).
    if (document != null && document.readyState === 'complete' && !iframeLoaded) {
      onLoad();
    }
  }, [iframeLoaded]);

  const document = frameRef.current?.contentDocument;
  return (
    <React.Fragment>
      <iframe className={classes.frame} onLoad={onLoad} ref={frameRef} title={title} {...other} />
      {iframeLoaded !== false
        ? ReactDOM.createPortal(
            <FramedDemo document={document}>{children}</FramedDemo>,
            document.body,
          )
        : null}
    </React.Fragment>
  );
}

DemoFrame.propTypes = {
  children: PropTypes.node.isRequired,
  title: PropTypes.string.isRequired,
};

/**
 * Isolates the demo component as best as possible. Additional props are spread
 * to an `iframe` if `iframe={true}`.
 */
function DemoSandboxed(props) {
  const { component: Component, iframe, name, onResetDemoClick, ...other } = props;
  const Sandbox = iframe ? DemoFrame : React.Fragment;
  const sandboxProps = iframe ? { title: `${name} demo`, ...other } : {};

  const t = useSelector((state) => state.options.t);

  return (
    <DemoErrorBoundary onResetDemoClick={onResetDemoClick} t={t}>
      <Sandbox {...sandboxProps}>
        <Component />
      </Sandbox>
    </DemoErrorBoundary>
  );
}

DemoSandboxed.propTypes = {
  component: PropTypes.elementType.isRequired,
  iframe: PropTypes.bool,
  name: PropTypes.string.isRequired,
  onResetDemoClick: PropTypes.func.isRequired,
};

export default React.memo(DemoSandboxed);
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013, David Farrell <shokku.ra@gmail.com>
 *  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * - Neither the name of iBot nor the names of its contributors may be used to
 *   endorse or promote products derived from this software without specific
 *   prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

var util = require('util');
var fs = require('fs');

var Server = require('./iBot-Server');

module.exports = function(configPath)
{
	this.servers = {};

	this.start = function()
	{
		for(var kServer in this.servers)
		{
			this.servers[kServer].connect();
		}
	}

	this.run = this.start;

	this.load = function(all)
	{
		fs.readFile(configPath, function(err, data)
		{
			if(err) console.log(err);
			else
			{
				try
				{
					this.config = JSON.parse(data);

					if(all === true)
					{
						if(this.config.servers !== undefined)
						{
							for(var kServer in this.config.servers)
							{
								this.servers[kServer] = new Server(this, this.config.servers[kServer]);
							}
						}

						if(this.config.modules !== undefined)
						{
							for(var kModule in this.config.modules)
							{
								this.loadModule(this.config.modules[kModule]);
							}
						}

						this.start();
						this.save();
					}
					else
					{
						if(this.config.servers !== undefined)
						{
							for(var kServer in this.config.servers)
							{
								this.servers[kServer].config = this.config.servers[kServer];
							}
						}
					}
				}
				catch(e)
				{
					console.log('Errors parsing config file:');
					console.log(e.stack);
				}
			}
		}.bind(this));
	}

	this.save = function()
	{
		var s = JSON.stringify(this.config, null, 2) + '\n';

		if(s !== undefined)
		{
			fs.writeFile(configPath, s, function(err)
			{
				if(err) console.log(err);
				else console.log('Saved config to ' + util.inspect(configPath));
			}.bind(this));
		}
	}

	this.loadModule = function(name, server)
	{
		var path;

		try
		{
			path = process.cwd() + '/modules/' + name;
			if(typeof require.cache[path] !== 'undefined') delete require.cache[path];
			require.resolve(path);
		}
		catch(e)
		{
			try
			{
				path = './modules/' + name;
				require.resolve(path);
			}
			catch(e2)
			{
				console.log(e.message);
				console.log(e2.message);
				return e.message + ' | ' + e2.message;
			}
		}

		try
		{
			if(typeof require.cache[require.resolve(path)] !== 'undefined')
			{
				delete require.cache[require.resolve(path)];
			}

			var module = require(path);

			if(typeof server === 'undefined' || server === null)
			{
				for(var kServer in this.servers)
				{
					var mod = new module.mod(this);
					this.servers[kServer].addModule(name, mod);
				}
			}
			else
			{
				var mod = new module.mod(this);
				server.addModule(name, mod);
			}

			console.log('Loaded module: ' + name);
			return '';
		}
		catch(e)
		{
			console.log('Failed to load module: ' + name);
			console.log(e.stack);
			return e.message;
		}
	}

	this.unloadModule = function(name, server)
	{
		if(server === undefined)
		{
			for(var kServer in this.servers)
			{
				this.servers[kServer].rmModule(name);
			}
		}
		else
		{
			server.rmModule(name);
		}

		console.log('Unloaded module: ' + name);
	}

	// JSON config

	this.load(true);
}
<commit_msg>Fixed local-to-iBot module hotswapping.<commit_after>/*
 * Copyright (c) 2013, David Farrell <shokku.ra@gmail.com>
 *  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * - Neither the name of iBot nor the names of its contributors may be used to
 *   endorse or promote products derived from this software without specific
 *   prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

var util = require('util');
var fs = require('fs');

var Server = require('./iBot-Server');

module.exports = function(configPath)
{
	this.servers = {};

	this.start = function()
	{
		for(var kServer in this.servers)
		{
			this.servers[kServer].connect();
		}
	}

	this.run = this.start;

	this.load = function(all)
	{
		fs.readFile(configPath, function(err, data)
		{
			if(err) console.log(err);
			else
			{
				try
				{
					this.config = JSON.parse(data);

					if(all === true)
					{
						if(this.config.servers !== undefined)
						{
							for(var kServer in this.config.servers)
							{
								this.servers[kServer] = new Server(this, this.config.servers[kServer]);
							}
						}

						if(this.config.modules !== undefined)
						{
							for(var kModule in this.config.modules)
							{
								this.loadModule(this.config.modules[kModule]);
							}
						}

						this.start();
						this.save();
					}
					else
					{
						if(this.config.servers !== undefined)
						{
							for(var kServer in this.config.servers)
							{
								this.servers[kServer].config = this.config.servers[kServer];
							}
						}
					}
				}
				catch(e)
				{
					console.log('Errors parsing config file:');
					console.log(e.stack);
				}
			}
		}.bind(this));
	}

	this.save = function()
	{
		var s = JSON.stringify(this.config, null, 2) + '\n';

		if(s !== undefined)
		{
			fs.writeFile(configPath, s, function(err)
			{
				if(err) console.log(err);
				else console.log('Saved config to ' + util.inspect(configPath));
			}.bind(this));
		}
	}

	this.loadModule = function(name, server)
	{
		var path = undefined;

		try
		{
			path = process.cwd() + '/modules/' + name;
			require.resolve(path);
			if(!fs.existsSync(require.resolve(path))) throw new Error('Local module does not exist');
		}
		catch(e1)
		{
			try
			{
				path = './modules/' + name;
				if(!fs.existsSync(require.resolve(path))) throw new Error('iBot module does not exist');
			}
			catch(e2)
			{
				console.log(e1.message);
				console.log(e2.message);
				return e.message + ' | ' + e2.message;
			}
		}

		try
		{
			if(typeof require.cache[require.resolve(path)] !== 'undefined') delete require.cache[require.resolve(path)];
			var module = require(path);

			if(typeof server === 'undefined' || server === null)
			{
				for(var kServer in this.servers)
				{
					var mod = new module.mod(this);
					this.servers[kServer].addModule(name, mod);
				}
			}
			else
			{
				var mod = new module.mod(this);
				server.addModule(name, mod);
			}

			console.log('Loaded module: ' + name);
			return '';
		}
		catch(e)
		{
			console.log('Failed to load module: ' + name);
			console.log(e.stack);
			return e.message;
		}
	}

	this.unloadModule = function(name, server)
	{
		if(server === undefined)
		{
			for(var kServer in this.servers)
			{
				this.servers[kServer].rmModule(name);
			}
		}
		else
		{
			server.rmModule(name);
		}

		console.log('Unloaded module: ' + name);
	}

	// JSON config

	this.load(true);
}
<|endoftext|>


<commit_before>const AbstractModule = require('./AbstractModule');
const {Client} = require('pg');
const {Permissions} = require('discord.js');
const later = require('later');
const async = require('async');

var draft = /Name: ([[\w \(\)]+)\s*Teams: ([^\n]+)\s*Rounds: (\d+)\s*Date: ([^\n]+)/im;
var insertDraft = 'INSERT INTO Drafts (Name, Teams, Rounds, Date, Guild, oChannel, Msg, Drafter) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING Draft_Key;';
var selectDrafts = 'SELECT Draft_Key, Name, Teams, Rounds, Date, Guild, oChannel, Msg FROM Drafts WHERE COALESCE(Channel, \'\') = \'\' AND Date BETWEEN $1 AND $2;';
var selectDraftsKey = 'SELECT Name, Teams, Rounds, Date FROM Drafts WHERE Draft_Key = $1';
var setDraftChannel = 'UPDATE Drafts SET Channel = $1, Role = $2 WHERE Draft_Key = $3;';
var updateDraft = 'UPDATE Drafts SET Name = $1, Teams = $2, Rounds = $3, Date = $4 WHERE Draft_Key = $5 RETURNING Draft_Key;';
var userDrafts = 'SELECT Guild, oChannel, Msg, Draft_Key FROM Drafts WHERE Drafter = $1 AND COALESCE(Channel, \'\') = \'\';';

class DraftScheduler extends AbstractModule {
  constructor() {
    super();
    this.pgClient = new Client({
      connectionString: process.env.DATABASE_URL,
      ssl: true,
    });
    this.pgClient.connect();

    var sched = later.parse.recur().on(12).hour();
    this.job = later.setInterval(this.updateDrafts, sched);
  }

  updateDrafts() {
    var today;
    var tomorrow;
    today = new Date();
    today = new Date(today.getFullYear(), today.getMonth(), today.getDate());
    tomorrow = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
    this.pgClient.query(selectDrafts, [today, tomorrow], (err, res) => {
      if (err) {
        console.log(err, res); return;
      }

      res.rows.reduce((acc, row) => {
        var chName = `draft-${row.name.replace(/\s+/g, '-')}`;
        var roleId;

        this.dClient.guilds.get(row.guild)
          .channels.get(row.ochannel)
          .messages.fetch(row.msg)
          .then(msg => {
            var ready = false;

            msg.guild.createRole({
              'data': {
                'name': `${row.name} Drafters`,
                'color': Math.floor(Math.random() * 256 * 256 * 256),
                'hoist': false,
                'position': 1,
                'permissions': 0,
                'mentionable': true
              },
              'reason': `People drafting ${row.name}`
            })
              .then(role => {

                async.parallel(
                  msg.reactions.map((acc, key, rxns) => {
                    return (callback) => {
                      rxns.get(key).fetchUsers().then(users => callback(null, users.array()));
                    };
                  }),
                  (err, results) => {
                    var drafters = results.reduce((acc, users) => {
                      users.reduce((l, user) => {
                        l.push(user);
                        return l;
                      }, acc);

                      return acc;
                    }, []);

                    async.parallel(
                      drafters.map(drafter => callback => {
                        msg.guild.member(drafter).addRole(role, `You're drafting ${row.name}`);
                        callback(null, drafter.username);
                      }),
                      (err, results) => {
                        msg.guild.createChannel(chName, 'text', {
                          'nsfw': false,
                          'parent': msg.channel.parent.id,
                          'reason': `Drafting channel for ${row.name}`
                        })
                          .then(ch => {
                            ch.send(`Hello <@!${role.id}>! This channel has been setup to draft ${row.name} today at ${row.date.toTimeString()}.
Teams competing can be found at the following link: ${row.teams}
Here are the drafters:
${results.join('\n')}`);
                            console.log(row.draft_key);
                            this.pgClient.query(setDraftChannel, [ch.id, role.id, row.draft_key]);
                          });
                      });
                  });
              });
          });
      }, 0);
    });
  }

  saveDraft(msg, draft_key, cb) {
    var r = draft.exec(msg.content);
    if (!r) {
      return false;
    }

    var name;
    var teams;
    var rounds;
    var date;
    var msgId;
    var chId;
    var guildId;
    var drafter;
    name = r[1];
    teams = r[2];
    rounds = parseInt(r[3]);
    date = new Date(new Date().getFullYear() + " " + r[4]);
    guildId = msg.channel.guild.id;
    chId = msg.channel.id;
    msgId = msg.id;
    drafter = msg.author.id;

    if (date.getHours() < 12) {
      date.setHours(date.getHours() + 12);
    }

    var query;
    var values;

    if (draft_key) {
      query = updateDraft;
      values = [name, teams, rounds, date, draft_key];
    } else {
      query = insertDraft;
      values = [name, teams, rounds, date, guildId, chId, msgId, drafter];
    }

    async.parallel(
      [callback => this.pgClient.query(query, values, (err, res) => {
        callback(err, res);
      })],
      (err, res) => {
        if (err) {
          console.log(err);
          return;
        }
        cb(res[0].rows[0].draft_key);
      });
  }

  getEvents() {
    return [
      {
        'key': 'message',
        'callback': message => {
          this.saveDraft(message, null, (key) => {
            this.pgClient.query(selectDraftsKey, [key], (err, res) => {
              message.channel.send(`Hey ${message.author.username}, I have you setup to run **${res.rows[0].name}** on **${res.rows[0].date}**.
It will be **${res.rows[0].rounds} rounds** of these teams: ${res.rows[0].teams}
I'll setup your channel and roles the day of.`);
            });
          });
        }
      },
      {
        'key': 'message',
        'callback': message => {
          var r = /!updateMyDrafts/i.exec(message.content);
          if (!r) {
            return;
          }

          this.pgClient.query(userDrafts, [message.author.id], (err, res) => {
            if (err) {
              console.log(err, res);
            }

            var msgs = res.rows.map(row => callback => {
              this.dClient.guilds.get(row.guild).channels.get(row.ochannel).messages.fetch(row.msg).then(msg => callback(null, {'key':row.draft_key, 'message':msg}));
            });

            async.parallel(
              msgs,
              (err, drafts) => drafts.map(draft => {
                this.saveDraft(draft.message, draft.key, (key) => {
                  this.pgClient.query(selectDraftsKey, [key], (err, res) => {
                    message.channel.send(`Hey ${message.author.username}, I've updated you draft of **${res.rows[0].name}** on **${res.rows[0].date}**.
It will be **${res.rows[0].rounds} rounds** of these teams: ${res.rows[0].teams}`);
                  });
                });
              }));
          });
        }
      }];
  }

  getEndpoints() {
    return {
      '/getDrafts': (request, response) => {
        while (!this.dClient.readyAt) {
          ;
        }
        this.updateDrafts(); response.send('Checking if there\'s drafts to make...');
      }
    };
  }
}

module.exports = DraftScheduler;<commit_msg>only update drafts when it's the only thing in the message lol<commit_after>const AbstractModule = require('./AbstractModule');
const {Client} = require('pg');
const {Permissions} = require('discord.js');
const later = require('later');
const async = require('async');

var draft = /Name: ([[\w \(\)]+)\s*Teams: ([^\n]+)\s*Rounds: (\d+)\s*Date: ([^\n]+)/im;
var insertDraft = 'INSERT INTO Drafts (Name, Teams, Rounds, Date, Guild, oChannel, Msg, Drafter) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING Draft_Key;';
var selectDrafts = 'SELECT Draft_Key, Name, Teams, Rounds, Date, Guild, oChannel, Msg FROM Drafts WHERE COALESCE(Channel, \'\') = \'\' AND Date BETWEEN $1 AND $2;';
var selectDraftsKey = 'SELECT Name, Teams, Rounds, Date FROM Drafts WHERE Draft_Key = $1';
var setDraftChannel = 'UPDATE Drafts SET Channel = $1, Role = $2 WHERE Draft_Key = $3;';
var updateDraft = 'UPDATE Drafts SET Name = $1, Teams = $2, Rounds = $3, Date = $4 WHERE Draft_Key = $5 RETURNING Draft_Key;';
var userDrafts = 'SELECT Guild, oChannel, Msg, Draft_Key FROM Drafts WHERE Drafter = $1 AND COALESCE(Channel, \'\') = \'\';';

class DraftScheduler extends AbstractModule {
  constructor() {
    super();
    this.pgClient = new Client({
      connectionString: process.env.DATABASE_URL,
      ssl: true,
    });
    this.pgClient.connect();

    var sched = later.parse.recur().on(12).hour();
    this.job = later.setInterval(this.updateDrafts, sched);
  }

  updateDrafts() {
    var today;
    var tomorrow;
    today = new Date();
    today = new Date(today.getFullYear(), today.getMonth(), today.getDate());
    tomorrow = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
    this.pgClient.query(selectDrafts, [today, tomorrow], (err, res) => {
      if (err) {
        console.log(err, res); return;
      }

      res.rows.reduce((acc, row) => {
        var chName = `draft-${row.name.replace(/\s+/g, '-')}`;
        var roleId;

        this.dClient.guilds.get(row.guild)
          .channels.get(row.ochannel)
          .messages.fetch(row.msg)
          .then(msg => {
            var ready = false;

            msg.guild.createRole({
              'data': {
                'name': `${row.name} Drafters`,
                'color': Math.floor(Math.random() * 256 * 256 * 256),
                'hoist': false,
                'position': 1,
                'permissions': 0,
                'mentionable': true
              },
              'reason': `People drafting ${row.name}`
            })
              .then(role => {

                async.parallel(
                  msg.reactions.map((acc, key, rxns) => {
                    return (callback) => {
                      rxns.get(key).fetchUsers().then(users => callback(null, users.array()));
                    };
                  }),
                  (err, results) => {
                    var drafters = results.reduce((acc, users) => {
                      users.reduce((l, user) => {
                        l.push(user);
                        return l;
                      }, acc);

                      return acc;
                    }, []);

                    async.parallel(
                      drafters.map(drafter => callback => {
                        msg.guild.member(drafter).addRole(role, `You're drafting ${row.name}`);
                        callback(null, drafter.username);
                      }),
                      (err, results) => {
                        msg.guild.createChannel(chName, 'text', {
                          'nsfw': false,
                          'parent': msg.channel.parent.id,
                          'reason': `Drafting channel for ${row.name}`
                        })
                          .then(ch => {
                            ch.send(`Hello <@!${role.id}>! This channel has been setup to draft ${row.name} today at ${row.date.toTimeString()}.
Teams competing can be found at the following link: ${row.teams}
Here are the drafters:
${results.join('\n')}`);
                            console.log(row.draft_key);
                            this.pgClient.query(setDraftChannel, [ch.id, role.id, row.draft_key]);
                          });
                      });
                  });
              });
          });
      }, 0);
    });
  }

  saveDraft(msg, draft_key, cb) {
    var r = draft.exec(msg.content);
    if (!r) {
      return false;
    }

    var name;
    var teams;
    var rounds;
    var date;
    var msgId;
    var chId;
    var guildId;
    var drafter;
    name = r[1];
    teams = r[2];
    rounds = parseInt(r[3]);
    date = new Date(new Date().getFullYear() + " " + r[4]);
    guildId = msg.channel.guild.id;
    chId = msg.channel.id;
    msgId = msg.id;
    drafter = msg.author.id;

    if (date.getHours() < 12) {
      date.setHours(date.getHours() + 12);
    }

    var query;
    var values;

    if (draft_key) {
      query = updateDraft;
      values = [name, teams, rounds, date, draft_key];
    } else {
      query = insertDraft;
      values = [name, teams, rounds, date, guildId, chId, msgId, drafter];
    }

    async.parallel(
      [callback => this.pgClient.query(query, values, (err, res) => {
        callback(err, res);
      })],
      (err, res) => {
        if (err) {
          console.log(err);
          return;
        }
        cb(res[0].rows[0].draft_key);
      });
  }

  getEvents() {
    return [
      {
        'key': 'message',
        'callback': message => {
          this.saveDraft(message, null, (key) => {
            this.pgClient.query(selectDraftsKey, [key], (err, res) => {
              message.channel.send(`Hey ${message.author.username}, I have you setup to run **${res.rows[0].name}** on **${res.rows[0].date}**.
It will be **${res.rows[0].rounds} rounds** of these teams: ${res.rows[0].teams}
I'll setup your channel and roles the day of.`);
            });
          });
        }
      },
      {
        'key': 'message',
        'callback': message => {
          var r = /^!updateMyDrafts$/i.exec(message.content);
          if (!r) {
            return;
          }

          this.pgClient.query(userDrafts, [message.author.id], (err, res) => {
            if (err) {
              console.log(err, res);
            }

            var msgs = res.rows.map(row => callback => {
              this.dClient.guilds.get(row.guild).channels.get(row.ochannel).messages.fetch(row.msg).then(msg => callback(null, {'key':row.draft_key, 'message':msg}));
            });

            async.parallel(
              msgs,
              (err, drafts) => drafts.map(draft => {
                this.saveDraft(draft.message, draft.key, (key) => {
                  this.pgClient.query(selectDraftsKey, [key], (err, res) => {
                    message.channel.send(`Hey ${message.author.username}, I've updated you draft of **${res.rows[0].name}** on **${res.rows[0].date}**.
It will be **${res.rows[0].rounds} rounds** of these teams: ${res.rows[0].teams}`);
                  });
                });
              }));
          });
        }
      }];
  }

  getEndpoints() {
    return {
      '/getDrafts': (request, response) => {
        while (!this.dClient.readyAt) {
          ;
        }
        this.updateDrafts(); response.send('Checking if there\'s drafts to make...');
      }
    };
  }
}

module.exports = DraftScheduler;<|endoftext|>