<commit_before>const _ = require('lodash');
const {Router} = require('express');
const body = require('body-parser');
const MagicLink = require('@tryghost/magic-link');
const StripePaymentProcessor = require('./lib/stripe');

const Tokens = require('./lib/tokens');
const Users = require('./lib/users');
const Metadata = require('./lib/metadata');
const common = require('./lib/common');

module.exports = function MembersApi({
    tokenConfig: {
        issuer,
        privateKey,
        publicKey
    },
    auth: {
        allowSelfSignup = true,
        getSigninURL,
        secret
    },
    paymentConfig,
    mail: {
        transporter,
        getText,
        getHTML,
        getSubject
    },
    memberStripeCustomerModel,
    stripeCustomerSubscriptionModel,
    memberModel,
    logger
}) {
    if (logger) {
        common.logging.setLogger(logger);
    }

    const {encodeIdentityToken, decodeToken} = Tokens({privateKey, publicKey, issuer});
    const metadata = Metadata({memberStripeCustomerModel, stripeCustomerSubscriptionModel});

    const stripeStorage = {
        async get(member) {
            return metadata.getMetadata('stripe', member);
        },
        async set(data) {
            return metadata.setMetadata('stripe', data);
        }
    };
    const stripe = paymentConfig.stripe ? new StripePaymentProcessor(paymentConfig.stripe, stripeStorage, common.logging) : null;

    async function ensureStripe(_req, res, next) {
        if (!stripe) {
            res.writeHead(400);
            return res.end('Stripe not configured');
        }
        try {
            await stripe.ready();
            next();
        } catch (err) {
            res.writeHead(500);
            return res.end('There was an error configuring stripe');
        }
    }

    const magicLinkService = new MagicLink({
        transporter,
        secret,
        getSigninURL,
        getText,
        getHTML,
        getSubject
    });

    async function sendEmailWithMagicLink(email, requestedType, options = {forceEmailType: false}){
        if (options.forceEmailType) {
            return magicLinkService.sendMagicLink({email, subject: email, type: requestedType});
        }
        const member = await users.get({email});
        if (member) {
            return magicLinkService.sendMagicLink({email, subject: email, type: 'signin'});
        } else {
            const type = requestedType === 'subscribe' ? 'subscribe' : 'signup';
            return magicLinkService.sendMagicLink({email, subject: email, type});
        }
    }

    const users = Users({
        stripe,
        memberModel
    });

    async function getMemberDataFromMagicLinkToken(token){
        const email = await magicLinkService.getUserFromToken(token);
        if (!email) {
            return null;
        }
        const member = await getMemberIdentityData(email);
        if (member) {
            return member;
        }
        await users.create({email});
        return getMemberIdentityData(email);
    }
    async function getMemberIdentityData(email){
        return users.get({email});
    }
    async function getMemberIdentityToken(email){
        const member = await getMemberIdentityData(email);
        if (!member) {
            return null;
        }
        return encodeIdentityToken({sub: member.email});
    }

    const middleware = {
        sendMagicLink: Router(),
        createCheckoutSession: Router(),
        handleStripeWebhook: Router(),
        updateSubscription: Router({mergeParams: true})
    };

    middleware.sendMagicLink.use(body.json(), async function (req, res) {
        const email = req.body.email;
        if (!email) {
            res.writeHead(400);
            return res.end('Bad Request.');
        }
        const emailType = req.body.emailType;
        try {
            if (!allowSelfSignup) {
                const member = await users.get({email});
                if (member) {
                    await sendEmailWithMagicLink(email, emailType);
                }
            } else {
                await sendEmailWithMagicLink(email, emailType);
            }
            res.writeHead(201);
            return res.end('Created.');
        } catch (err) {
            common.logging.error(err);
            res.writeHead(500);
            return res.end('Internal Server Error.');
        }
    });

    middleware.createCheckoutSession.use(ensureStripe, body.json(), async function (req, res) {
        const plan = req.body.plan;
        const identity = req.body.identity;

        if (!plan) {
            res.writeHead(400);
            return res.end('Bad Request.');
        }

        let email;
        try {
            if (!identity) {
                email = null;
            } else {
                const claims = await decodeToken(identity);
                email = claims.sub;
            }
        } catch (err) {
            res.writeHead(401);
            return res.end('Unauthorized');
        }

        const member = email ? await users.get({email}) : null;

        // Do not allow members already with a subscription to initiate a new checkout session
        if (member && member.stripe.subscriptions.length > 0) {
            res.writeHead(403);
            return res.end('No permission');
        }

        const sessionInfo = await stripe.createCheckoutSession(member, plan, {
            successUrl: req.body.successUrl,
            cancelUrl: req.body.cancelUrl
        });

        res.writeHead(200, {
            'Content-Type': 'application/json'
        });

        res.end(JSON.stringify(sessionInfo));
    });

    middleware.handleStripeWebhook.use(ensureStripe, body.raw({type: 'application/json'}), async function (req, res) {
        let event;
        try {
            event = await stripe.parseWebhook(req.body, req.headers['stripe-signature']);
        } catch (err) {
            common.logging.error(err);
            res.writeHead(401);
            return res.end();
        }
        try {
            if (event.type === 'customer.subscription.deleted') {
                await stripe.handleCustomerSubscriptionDeletedWebhook(event.data.object);
            }

            if (event.type === 'customer.subscription.updated') {
                await stripe.handleCustomerSubscriptionUpdatedWebhook(event.data.object);
            }

            if (event.type === 'invoice.payment_succeeded') {
                await stripe.handleInvoicePaymentSucceededWebhook(event.data.object);
            }

            if (event.type === 'invoice.payment_failed') {
                await stripe.handleInvoicePaymentFailedWebhook(event.data.object);
            }

            if (event.type === 'checkout.session.completed') {
                const customer = await stripe.getCustomer(event.data.object.customer, {
                    expand: ['subscriptions.data.default_payment_method']
                });

                const member = await users.get({email: customer.email}) || await users.create({email: customer.email});
                await stripe.handleCheckoutSessionCompletedWebhook(member, customer);

                const payerName = _.get(customer, 'subscriptions.data[0].default_payment_method.billing_details.name');

                if (payerName && member.name === '') {
                    await users.update({name: payerName}, {id: member.id});
                }

                const emailType = 'signup';
                await sendEmailWithMagicLink(customer.email, emailType, {forceEmailType: true});
            }

            res.writeHead(200);
            res.end();
        } catch (err) {
            common.logging.error(`Error handling webhook ${event.type}`, err);
            res.writeHead(400);
            res.end();
        }
    });

    middleware.updateSubscription.use(ensureStripe, body.json(), async function (req, res) {
        const identity = req.body.identity;
        const cancelAtPeriodEnd = req.body.cancel_at_period_end;
        const subscriptionId = req.params.id;

        let member;

        try {
            if (!identity) {
                throw new common.errors.BadRequestError({
                    message: 'Cancel membership failed! Could not find member'
                });
            }

            const claims = await decodeToken(identity);
            const email = claims.sub;
            member = email ? await users.get({email}) : null;

            if (!member) {
                throw new common.errors.BadRequestError({
                    message: 'Cancel membership failed! Could not find member'
                });
            }
        } catch (err) {
            res.writeHead(401);
            return res.end('Unauthorized');
        }

        // Don't allow removing subscriptions that don't belong to the member
        const subscription = member.stripe.subscriptions.find(sub => sub.id === subscriptionId);

        if (!subscription) {
            res.writeHead(403);
            return res.end('No permission');
        }

        if (cancelAtPeriodEnd === undefined) {
            throw new common.errors.BadRequestError({
                message: 'Canceling membership failed!',
                help: 'Request should contain boolean "cancel" field.'
            });
        }

        subscription.cancel_at_period_end = !!(cancelAtPeriodEnd);

        await stripe.updateSubscriptionFromClient(subscription);

        res.writeHead(204);
        res.end();
    });

    const getPublicConfig = function () {
        return Promise.resolve({
            publicKey,
            issuer
        });
    };

    const bus = new (require('events').EventEmitter)();

    if (stripe) {
        stripe.ready().then(() => {
            bus.emit('ready');
        }).catch((err) => {
            bus.emit('error', err);
        });
    } else {
        process.nextTick(() => bus.emit('ready'));
    }

    return {
        middleware,
        getMemberDataFromMagicLinkToken,
        getMemberIdentityToken,
        getMemberIdentityData,
        getPublicConfig,
        bus,
        sendEmailWithMagicLink,
        members: users
    };
};
<commit_msg>Fixed preexisting member name check<commit_after>const _ = require('lodash');
const {Router} = require('express');
const body = require('body-parser');
const MagicLink = require('@tryghost/magic-link');
const StripePaymentProcessor = require('./lib/stripe');

const Tokens = require('./lib/tokens');
const Users = require('./lib/users');
const Metadata = require('./lib/metadata');
const common = require('./lib/common');

module.exports = function MembersApi({
    tokenConfig: {
        issuer,
        privateKey,
        publicKey
    },
    auth: {
        allowSelfSignup = true,
        getSigninURL,
        secret
    },
    paymentConfig,
    mail: {
        transporter,
        getText,
        getHTML,
        getSubject
    },
    memberStripeCustomerModel,
    stripeCustomerSubscriptionModel,
    memberModel,
    logger
}) {
    if (logger) {
        common.logging.setLogger(logger);
    }

    const {encodeIdentityToken, decodeToken} = Tokens({privateKey, publicKey, issuer});
    const metadata = Metadata({memberStripeCustomerModel, stripeCustomerSubscriptionModel});

    const stripeStorage = {
        async get(member) {
            return metadata.getMetadata('stripe', member);
        },
        async set(data) {
            return metadata.setMetadata('stripe', data);
        }
    };
    const stripe = paymentConfig.stripe ? new StripePaymentProcessor(paymentConfig.stripe, stripeStorage, common.logging) : null;

    async function ensureStripe(_req, res, next) {
        if (!stripe) {
            res.writeHead(400);
            return res.end('Stripe not configured');
        }
        try {
            await stripe.ready();
            next();
        } catch (err) {
            res.writeHead(500);
            return res.end('There was an error configuring stripe');
        }
    }

    const magicLinkService = new MagicLink({
        transporter,
        secret,
        getSigninURL,
        getText,
        getHTML,
        getSubject
    });

    async function sendEmailWithMagicLink(email, requestedType, options = {forceEmailType: false}){
        if (options.forceEmailType) {
            return magicLinkService.sendMagicLink({email, subject: email, type: requestedType});
        }
        const member = await users.get({email});
        if (member) {
            return magicLinkService.sendMagicLink({email, subject: email, type: 'signin'});
        } else {
            const type = requestedType === 'subscribe' ? 'subscribe' : 'signup';
            return magicLinkService.sendMagicLink({email, subject: email, type});
        }
    }

    const users = Users({
        stripe,
        memberModel
    });

    async function getMemberDataFromMagicLinkToken(token){
        const email = await magicLinkService.getUserFromToken(token);
        if (!email) {
            return null;
        }
        const member = await getMemberIdentityData(email);
        if (member) {
            return member;
        }
        await users.create({email});
        return getMemberIdentityData(email);
    }
    async function getMemberIdentityData(email){
        return users.get({email});
    }
    async function getMemberIdentityToken(email){
        const member = await getMemberIdentityData(email);
        if (!member) {
            return null;
        }
        return encodeIdentityToken({sub: member.email});
    }

    const middleware = {
        sendMagicLink: Router(),
        createCheckoutSession: Router(),
        handleStripeWebhook: Router(),
        updateSubscription: Router({mergeParams: true})
    };

    middleware.sendMagicLink.use(body.json(), async function (req, res) {
        const email = req.body.email;
        if (!email) {
            res.writeHead(400);
            return res.end('Bad Request.');
        }
        const emailType = req.body.emailType;
        try {
            if (!allowSelfSignup) {
                const member = await users.get({email});
                if (member) {
                    await sendEmailWithMagicLink(email, emailType);
                }
            } else {
                await sendEmailWithMagicLink(email, emailType);
            }
            res.writeHead(201);
            return res.end('Created.');
        } catch (err) {
            common.logging.error(err);
            res.writeHead(500);
            return res.end('Internal Server Error.');
        }
    });

    middleware.createCheckoutSession.use(ensureStripe, body.json(), async function (req, res) {
        const plan = req.body.plan;
        const identity = req.body.identity;

        if (!plan) {
            res.writeHead(400);
            return res.end('Bad Request.');
        }

        let email;
        try {
            if (!identity) {
                email = null;
            } else {
                const claims = await decodeToken(identity);
                email = claims.sub;
            }
        } catch (err) {
            res.writeHead(401);
            return res.end('Unauthorized');
        }

        const member = email ? await users.get({email}) : null;

        // Do not allow members already with a subscription to initiate a new checkout session
        if (member && member.stripe.subscriptions.length > 0) {
            res.writeHead(403);
            return res.end('No permission');
        }

        const sessionInfo = await stripe.createCheckoutSession(member, plan, {
            successUrl: req.body.successUrl,
            cancelUrl: req.body.cancelUrl
        });

        res.writeHead(200, {
            'Content-Type': 'application/json'
        });

        res.end(JSON.stringify(sessionInfo));
    });

    middleware.handleStripeWebhook.use(ensureStripe, body.raw({type: 'application/json'}), async function (req, res) {
        let event;
        try {
            event = await stripe.parseWebhook(req.body, req.headers['stripe-signature']);
        } catch (err) {
            common.logging.error(err);
            res.writeHead(401);
            return res.end();
        }
        try {
            if (event.type === 'customer.subscription.deleted') {
                await stripe.handleCustomerSubscriptionDeletedWebhook(event.data.object);
            }

            if (event.type === 'customer.subscription.updated') {
                await stripe.handleCustomerSubscriptionUpdatedWebhook(event.data.object);
            }

            if (event.type === 'invoice.payment_succeeded') {
                await stripe.handleInvoicePaymentSucceededWebhook(event.data.object);
            }

            if (event.type === 'invoice.payment_failed') {
                await stripe.handleInvoicePaymentFailedWebhook(event.data.object);
            }

            if (event.type === 'checkout.session.completed') {
                const customer = await stripe.getCustomer(event.data.object.customer, {
                    expand: ['subscriptions.data.default_payment_method']
                });

                const member = await users.get({email: customer.email}) || await users.create({email: customer.email});
                await stripe.handleCheckoutSessionCompletedWebhook(member, customer);

                const payerName = _.get(customer, 'subscriptions.data[0].default_payment_method.billing_details.name');

                if (payerName && !member.name) {
                    await users.update({name: payerName}, {id: member.id});
                }

                const emailType = 'signup';
                await sendEmailWithMagicLink(customer.email, emailType, {forceEmailType: true});
            }

            res.writeHead(200);
            res.end();
        } catch (err) {
            common.logging.error(`Error handling webhook ${event.type}`, err);
            res.writeHead(400);
            res.end();
        }
    });

    middleware.updateSubscription.use(ensureStripe, body.json(), async function (req, res) {
        const identity = req.body.identity;
        const cancelAtPeriodEnd = req.body.cancel_at_period_end;
        const subscriptionId = req.params.id;

        let member;

        try {
            if (!identity) {
                throw new common.errors.BadRequestError({
                    message: 'Cancel membership failed! Could not find member'
                });
            }

            const claims = await decodeToken(identity);
            const email = claims.sub;
            member = email ? await users.get({email}) : null;

            if (!member) {
                throw new common.errors.BadRequestError({
                    message: 'Cancel membership failed! Could not find member'
                });
            }
        } catch (err) {
            res.writeHead(401);
            return res.end('Unauthorized');
        }

        // Don't allow removing subscriptions that don't belong to the member
        const subscription = member.stripe.subscriptions.find(sub => sub.id === subscriptionId);

        if (!subscription) {
            res.writeHead(403);
            return res.end('No permission');
        }

        if (cancelAtPeriodEnd === undefined) {
            throw new common.errors.BadRequestError({
                message: 'Canceling membership failed!',
                help: 'Request should contain boolean "cancel" field.'
            });
        }

        subscription.cancel_at_period_end = !!(cancelAtPeriodEnd);

        await stripe.updateSubscriptionFromClient(subscription);

        res.writeHead(204);
        res.end();
    });

    const getPublicConfig = function () {
        return Promise.resolve({
            publicKey,
            issuer
        });
    };

    const bus = new (require('events').EventEmitter)();

    if (stripe) {
        stripe.ready().then(() => {
            bus.emit('ready');
        }).catch((err) => {
            bus.emit('error', err);
        });
    } else {
        process.nextTick(() => bus.emit('ready'));
    }

    return {
        middleware,
        getMemberDataFromMagicLinkToken,
        getMemberIdentityToken,
        getMemberIdentityData,
        getPublicConfig,
        bus,
        sendEmailWithMagicLink,
        members: users
    };
};
<|endoftext|>


<commit_before>let state = {
  todo: [],
  selectedTab: ''
};

onPageLoad();

// - State ------------------------------------------------
function setState(newState) {
  Object.assign(state, newState);

  render(state);

  save(state);
}

// - Actions ------------------------------------------------

function onPageLoad() {
  setState(getSavedState() || {todo: [], selectedTab: 'all'});
}

function onInputChange(eventObject) {
  setState({todo: addItemToList(state.todo, eventObject.target.value)});
}

function onCheckboxChange(eventObject) {
  document.addEventListener('transitionend', onTransitionEnd);

  function onTransitionEnd() {

    setState({todo: toggleItemStatus(state.todo, eventObject.target.dataset.id)});

    document.removeEventListener('transitionend', onTransitionEnd);
  }
}

function onRemoveButtonClick(eventObject) {
  setState({todo: deleteItemFromList(state.todo, eventObject.target.dataset.id)});
}

function onTabClick(eventObject) {
  setState({todo: state.todo, selectedTab: eventObject.target.id});
}

function onClearCompletedClick() {
  setState({todo: getItemsByStatus(state.todo, 'active')});
}

function onNameClick(eventObject) {
  eventObject.preventDefault();
}

function onNameDoubleClick(eventObject) {
  eventObject.preventDefault();

  setState({todo: toggleEditableState(state.todo, eventObject.target.dataset.id)})
}

function onNameKeyPress(eventObject) {
  if (eventObject.key === 'Enter') {
    onNameDoubleClick(eventObject);
  }
}

function onNameChange(eventObject) {
  let id = eventObject.target.dataset.id;
  let todo = toggleEditableState(state.todo, id);
  todo = renameItem(todo, id, eventObject.target.value);

  setState({todo});
}

// - View ------------------------------------------------
function render(state) {
  let InputContainer = document.querySelector('.InputContainer');
  let ListContainer = document.querySelector('.ListContainer');
  let FooterContainer = document.querySelector('.FooterContainer');

  InputContainer.innerHTML = TodoInput(state);
  TodoInputAfterRender(document.querySelector('header'));

  ListContainer.innerHTML = TodoList(state);
  TodoListAfterRender(document.querySelector('.List'));

  FooterContainer.innerHTML = TodoFooter(state);
  TodoFooterAfterRender(document.querySelector('footer'));
}

function TodoInput() {
  return `

      <input class="TodoInput Item" type="text" placeholder="What needs to be done?"
             onchange="onInputChange(event)">
    `;
}

function TodoInputAfterRender(header) {
  header.querySelector('input').focus();
}

function TodoFooter(state) {
  let numItems = getItemsByStatus(state.todo, 'active').length;
  let id = tab => tab.toLowerCase();
  return `
    ${numItems} items left

      ${['All', 'Active', 'Completed'].map((tab) => `

        ${tab}

`
    ).join('')}

    <button class="text-decoration:hover" onclick="onClearCompletedClick(event)">
      Clear completed
    </button>
  `;
}
function TodoFooterAfterRender() {}

function TodoList(state) {
  let id = todoItem => getItemId(todoItem.name);
  let filteredTodo = getItemsByStatus(state.todo, state.selectedTab);
  return `

    ${filteredTodo.map(todoItem => `

        <input type="checkbox" name="" id="${id(todoItem)}" data-id="${id(todoItem)}"
              ${todoItem.active === false && 'checked'}
              onchange="onCheckboxChange(event)">

        ${todoItem.isEditable ? `
          <input type="text" class="TodoInput" data-id="${id(todoItem)}" value="${todoItem.name}"
                onchange="onNameChange(event)"
        ` : `
          ${todoItem.name}
        `}

        <button class="Item-remove flex-alignSelf--right" aria-label="Remove"
                data-id="${id(todoItem)}"
                onclick="onRemoveButtonClick(event)">×</button>

`).join('')}

`;
}
function TodoListAfterRender(ul) {
  let editInput = ul.querySelector('input[type="text"]');
  if (editInput) {
    editInput.focus();
  }
}

// - Todo Model ------------------------------------------------

function getItemId(name) {
  return 'id-' + name;
}

function getItemsByStatus(todo, status) {
  let filteredItems = [];
  for (let i = 0; i < todo.length; i++) {
    if (status === 'all' || status === 'active' && todo[i].active) {
      filteredItems.push(todo[i]);
    } else if (status === 'all' || status === 'completed' && !todo[i].active) {
      filteredItems.push(todo[i]);
    }
  }
  return filteredItems;
}

function addItemToList(todo, name){
  let newItem = {name: name, active: true, id: getItemId(name)};
  todo.push(newItem);
  return todo;
}

function deleteItemFromList(todo, id){
  let item = findById(todo, id);

  // getting the item's index
  let idx = todo.indexOf(item);
  // deleting the item from the array
  todo.splice(idx, 1);
  return todo;
}

function renameItem(todo, id, newName){
  let item = findById(todo, id);
  item.name = newName;
  item.id = getItemId(newName);
  return todo;
}

function toggleItemStatus(todo, id){
  let item = findById(todo, id);
  item.active = !item.active;
  return todo;
}

function toggleEditableState(todo, id){
  let item = findById(todo, id);
  item.isEditable = !item.isEditable;
  return todo;
}

function findById(todo, id) {
  // return todo.find(item => item.id === id);
  return todo.filter(item => item.id === id)[0];
}

// - Storage ------------------------------------------------
function save(state) {
  localStorage.setItem('state', JSON.stringify(state));
}

function getSavedState() {
  let value = localStorage.getItem('state');
  if (value) {
    return JSON.parse(value);
  } else {
    return null;
  }
}<commit_msg>reduce more code<commit_after>let state = {
  todo: [],
  selectedTab: ''
};

onPageLoad();

// - State ------------------------------------------------
function setState(newState) {
  Object.assign(state, newState);

  render(state);

  save(state);
}

// - Actions ------------------------------------------------

function onPageLoad() {
  setState(getSavedState() || {todo: [], selectedTab: 'all'});
}

function onInputChange(eventObject) {
  setState({todo: addItemToList(state.todo, eventObject.target.value)});
}

function onCheckboxChange(eventObject) {
  document.addEventListener('transitionend', onTransitionEnd);

  function onTransitionEnd() {

    setState({todo: toggleItemStatus(state.todo, eventObject.target.dataset.id)});

    document.removeEventListener('transitionend', onTransitionEnd);
  }
}

function onRemoveButtonClick(eventObject) {
  setState({todo: deleteItemFromList(state.todo, eventObject.target.dataset.id)});
}

function onTabClick(eventObject) {
  setState({todo: state.todo, selectedTab: eventObject.target.id});
}

function onClearCompletedClick() {
  setState({todo: getItemsByStatus(state.todo, 'active')});
}

function onNameClick(eventObject) {
  eventObject.preventDefault();
}

function onNameDoubleClick(eventObject) {
  eventObject.preventDefault();

  setState({todo: toggleEditableState(state.todo, eventObject.target.dataset.id)})
}

function onNameKeyPress(eventObject) {
  if (eventObject.key === 'Enter') {
    onNameDoubleClick(eventObject);
  }
}

function onNameChange(eventObject) {
  let id = eventObject.target.dataset.id;
  let todo = toggleEditableState(state.todo, id);
  todo = renameItem(todo, id, eventObject.target.value);

  setState({todo});
}

// - View ------------------------------------------------
function render(state) {
  let InputContainer = document.querySelector('.InputContainer');
  let ListContainer = document.querySelector('.ListContainer');
  let FooterContainer = document.querySelector('.FooterContainer');

  InputContainer.innerHTML = TodoInput(state);
  TodoInputAfterRender(document.querySelector('header'));

  ListContainer.innerHTML = TodoList(state);
  TodoListAfterRender(document.querySelector('.List'));

  FooterContainer.innerHTML = TodoFooter(state);
  TodoFooterAfterRender(document.querySelector('footer'));
}

function TodoInput() {
  return `

      <input class="TodoInput Item" type="text" placeholder="What needs to be done?"
             onchange="onInputChange(event)">
    `;
}

function TodoInputAfterRender(header) {
  header.querySelector('input').focus();
}

function TodoFooter(state) {
  let numItems = getItemsByStatus(state.todo, 'active').length;
  let id = tab => tab.toLowerCase();
  return `
    ${numItems} items left

      ${['All', 'Active', 'Completed'].map((tab) => `

        ${tab}

`
    ).join('')}

    <button class="text-decoration:hover" onclick="onClearCompletedClick(event)">
      Clear completed
    </button>
  `;
}
function TodoFooterAfterRender() {}

function TodoList(state) {
  let id = todoItem => getItemId(todoItem.name);
  let filteredTodo = getItemsByStatus(state.todo, state.selectedTab);
  return `

    ${filteredTodo.map(todoItem => `

        <input type="checkbox" name="" id="${id(todoItem)}" data-id="${id(todoItem)}"
              ${todoItem.active === false && 'checked'}
              onchange="onCheckboxChange(event)">

        ${todoItem.isEditable ? `
          <input type="text" class="TodoInput" data-id="${id(todoItem)}" value="${todoItem.name}"
                onchange="onNameChange(event)"
        ` : `
          ${todoItem.name}
        `}

        <button class="Item-remove flex-alignSelf--right" aria-label="Remove"
                data-id="${id(todoItem)}"
                onclick="onRemoveButtonClick(event)">×</button>

`).join('')}

`;
}
function TodoListAfterRender(ul) {
  let editInput = ul.querySelector('input[type="text"]');
  if (editInput) {
    editInput.focus();
  }
}

// - Todo Model ------------------------------------------------

function getItemId(name) {
  return 'id-' + name;
}

function getItemsByStatus(todo, status) {
  return status === 'all' ? todo : todo.filter(item => {
    return status === 'active' && item.active || status === 'completed' && !item.active;
  });
}

function addItemToList(todo, name){
  let newItem = {name: name, active: true, id: getItemId(name)};
  todo.push(newItem);
  return todo;
}

function deleteItemFromList(todo, id){
  let item = findById(todo, id);

  let idx = todo.indexOf(item);
  todo.splice(idx, 1);
  return todo;
}

function renameItem(todo, id, newName){
  let item = findById(todo, id);
  item.name = newName;
  item.id = getItemId(newName);
  return todo;
}

function toggleItemStatus(todo, id){
  let item = findById(todo, id);
  item.active = !item.active;
  return todo;
}

function toggleEditableState(todo, id){
  let item = findById(todo, id);
  item.isEditable = !item.isEditable;
  return todo;
}

function findById(todo, id) {
  // return todo.find(item => item.id === id);
  return todo.filter(item => item.id === id)[0];
}

// - Storage ------------------------------------------------
function save(state) {
  localStorage.setItem('state', JSON.stringify(state));
}

function getSavedState() {
  let value = localStorage.getItem('state');
  return value ? JSON.parse(value) : null;
}<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import Modal from 'react-bootstrap/lib/Modal';
import ButtonGroup from 'react-bootstrap/lib/ButtonGroup';
import Button from 'react-bootstrap/lib/Button';
import Alert from 'react-bootstrap/lib/Alert';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import Slider from 'react-rangeslider';
import 'react-rangeslider/lib/index.css';

import { mapSetTileFormat, mapSetOverlayOpacity, setMouseCursorToCrosshair, resetMouseCursor } from 'fm3/actions/mapActions';
import { setTool, setHomeLocation, closePopup, setExpertMode } from 'fm3/actions/mainActions';
import { toastsAdd } from 'fm3/actions/toastsActions';

import FontAwesomeIcon from 'fm3/components/FontAwesomeIcon';
import { formatGpsCoord } from 'fm3/geoutils';
import mapEventEmitter from 'fm3/emitters/mapEventEmitter';
import * as FmPropTypes from 'fm3/propTypes';

class Settings extends React.Component {

  static propTypes = {
    homeLocation: PropTypes.shape({
      lat: PropTypes.number,
      lon: PropTypes.number,
    }),
    tileFormat: FmPropTypes.tileFormat.isRequired,
    onSave: PropTypes.func.isRequired,
    onClosePopup: PropTypes.func.isRequired,
    onSelectHomeLocation: PropTypes.func.isRequired,
    onSelectHomeLocationFinished: PropTypes.func.isRequired,
    tool: FmPropTypes.tool,
    nlcOpacity: PropTypes.number.isRequired,
    touristOverlayOpacity: PropTypes.number.isRequired,
    cycloOverlayOpacity: PropTypes.number.isRequired,
    zoom: PropTypes.number,
    expertMode: PropTypes.bool.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      homeLocationCssClasses: '',
      tileFormat: props.tileFormat,
      homeLocation: props.homeLocation,
      nlcOpacity: props.nlcOpacity,
      touristOverlayOpacity: props.touristOverlayOpacity,
      cycloOverlayOpacity: props.cycloOverlayOpacity,
      expertMode: props.expertMode,
    };
  }

  componentWillMount() {
    mapEventEmitter.on('mapClick', this.onHomeLocationSelected);
  }

  componentWillUnmount() {
    mapEventEmitter.removeListener('mapClick', this.onHomeLocationSelected);
  }

  onHomeLocationSelected = (lat, lon) => {
    this.setState({ homeLocation: { lat, lon }, homeLocationCssClasses: 'animated flash' }); // via animate.css
    this.props.onSelectHomeLocationFinished();
  }

  handleSave = () => {
    this.props.onSave(this.state.tileFormat,
      this.state.homeLocation,
      this.state.nlcOpacity,
      this.state.touristOverlayOpacity,
      this.state.cycloOverlayOpacity,
      this.state.expertMode,
    );
  }

  render() {
    const { onClosePopup, onSelectHomeLocation, tool, zoom } = this.props;
    const { homeLocation, homeLocationCssClasses } = this.state;
    const nlcOverlayIsNotVisible = zoom < 14;

    const userMadeChanges = ['tileFormat', 'homeLocation', 'nlcOpacity', 'touristOverlayOpacity', 'cycloOverlayOpacity', 'expertMode']
      .some(prop => this.state[prop] !== this.props[prop]);

    let homeLocationInfo = 'neurčená';
    if (homeLocation.lat && homeLocation.lon) {
      homeLocationInfo = `${formatGpsCoord(homeLocation.lat, 'SN')} ${formatGpsCoord(homeLocation.lon, 'WE')}`;
    }

    return (
      <Modal show={tool !== 'select-home-location'} onHide={onClosePopup}>
        <Modal.Header closeButton>
          <Modal.Title>Nastavenia</Modal.Title>
        </Modal.Header>
        <Modal.Body>

            Formát dlaždíc:

            <ButtonGroup>
              <Button
                active={this.state.tileFormat === 'png'}
                onClick={() => this.setState({ tileFormat: 'png' })}
              >
                PNG
              </Button>
              <Button
                active={this.state.tileFormat === 'jpeg'}
                onClick={() => this.setState({ tileFormat: 'jpeg' })}
              >
                JPG
              </Button>
            </ButtonGroup>

          <Alert>
            Mapové dlaždice vyzerajú lepšie v PNG formáte, ale sú asi 4x väčšie než JPG dlaždice.
            Pri pomalom internete preto odporúčame zvoliť JPG.
          </Alert>

          Domovská poloha: {homeLocationInfo}

          <Button onClick={() => onSelectHomeLocation()}>
            <FontAwesomeIcon icon="crosshairs" /> Vybrať na mape
          </Button>

          Viditeľnosť vrstvy Lesné cesty NLC: {this.state.nlcOpacity.toFixed(1) * 100}%
          <Slider
            value={this.state.nlcOpacity}
            min={0.1}
            max={1.0}
            step={0.1}
            tooltip={false}
            onChange={newOpacity => this.setState({ nlcOpacity: newOpacity })}
          />
          {nlcOverlayIsNotVisible &&
            <Alert>
              NLC vrstva sa zobrazuje až pri detailnejšom priblížení (od zoom úrovne 14).
            </Alert>
          }

            Expertný mód:

            <ButtonGroup>
              <Button
                active={!this.state.expertMode}
                onClick={() => this.setState({ expertMode: false })}
              >
                Vypnutý
              </Button>
              <Button
                active={this.state.expertMode}
                onClick={() => this.setState({ expertMode: true })}
              >
                Zapnutý
              </Button>
            </ButtonGroup>

          {!this.state.expertMode &&
            <Alert>
              V expertnom móde sú dostupné nástroje pre pokročilých používateľov.
            </Alert>
          }
          {this.state.expertMode &&

                Viditeľnosť vrstvy Turistické trasy: {(this.state.touristOverlayOpacity).toFixed(1) * 100}%
              <Slider
                value={this.state.touristOverlayOpacity}
                min={0.1}
                max={1.0}
                step={0.1}
                tooltip={false}
                onChange={newOpacity => this.setState({ touristOverlayOpacity: newOpacity })}
              />

          }
          {this.state.expertMode &&

                Viditeľnosť vrstvy Cyklotrasy: {(this.state.cycloOverlayOpacity).toFixed(1) * 100}%
              <Slider
                value={this.state.cycloOverlayOpacity}
                min={0.1}
                max={1.0}
                step={0.1}
                tooltip={false}
                onChange={newOpacity => this.setState({ cycloOverlayOpacity: newOpacity })}
              />

          }
        </Modal.Body>
        <Modal.Footer>
          <Button bsStyle="info" onClick={this.handleSave} disabled={!userMadeChanges}><Glyphicon glyph="floppy-disk" /> Uložiť</Button>
          <Button onClick={onClosePopup}><Glyphicon glyph="remove" /> Zrušiť</Button>
        </Modal.Footer>
      </Modal>
    );
  }
}

export default connect(
  state => ({
    tileFormat: state.map.tileFormat,
    homeLocation: state.main.homeLocation,
    tool: state.main.tool,
    zoom: state.map.zoom,
    nlcOpacity: state.map.overlayOpacity.N,
    touristOverlayOpacity: state.map.overlayOpacity.t,
    cycloOverlayOpacity: state.map.overlayOpacity.c,
    expertMode: state.main.expertMode,
  }),
  dispatch => ({
    onSave(tileFormat, homeLocation, nlcOpacity, touristOverlayOpacity, cycloOverlayOpacity, expertMode) {
      // TODO replace with signle dispatch (for goot pratcice)
      dispatch(mapSetTileFormat(tileFormat));
      dispatch(setHomeLocation(homeLocation));
      dispatch(mapSetOverlayOpacity('N', nlcOpacity));
      dispatch(mapSetOverlayOpacity('t', touristOverlayOpacity));
      dispatch(mapSetOverlayOpacity('c', cycloOverlayOpacity));
      dispatch(closePopup());
      dispatch(setExpertMode(expertMode));
      dispatch(toastsAdd({
        collapseKey: 'settings.saved',
        message: 'Zmeny boli uložené.',
        style: 'info',
        timeout: 3000,
      }));
    },
    onClosePopup() {
      dispatch(closePopup());
    },
    onSelectHomeLocation() {
      // TODO replace with signle dispatch (for goot pratcice)
      dispatch(setTool('select-home-location'));
      dispatch(setMouseCursorToCrosshair());
    },
    onSelectHomeLocationFinished() {
      // TODO replace with signle dispatch (for goot pratcice)
      dispatch(setTool(null));
      dispatch(resetMouseCursor());
    },
  }),
)(Settings);
<commit_msg>fixed typo in comments<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import Modal from 'react-bootstrap/lib/Modal';
import ButtonGroup from 'react-bootstrap/lib/ButtonGroup';
import Button from 'react-bootstrap/lib/Button';
import Alert from 'react-bootstrap/lib/Alert';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import Slider from 'react-rangeslider';
import 'react-rangeslider/lib/index.css';

import { mapSetTileFormat, mapSetOverlayOpacity, setMouseCursorToCrosshair, resetMouseCursor } from 'fm3/actions/mapActions';
import { setTool, setHomeLocation, closePopup, setExpertMode } from 'fm3/actions/mainActions';
import { toastsAdd } from 'fm3/actions/toastsActions';

import FontAwesomeIcon from 'fm3/components/FontAwesomeIcon';
import { formatGpsCoord } from 'fm3/geoutils';
import mapEventEmitter from 'fm3/emitters/mapEventEmitter';
import * as FmPropTypes from 'fm3/propTypes';

class Settings extends React.Component {

  static propTypes = {
    homeLocation: PropTypes.shape({
      lat: PropTypes.number,
      lon: PropTypes.number,
    }),
    tileFormat: FmPropTypes.tileFormat.isRequired,
    onSave: PropTypes.func.isRequired,
    onClosePopup: PropTypes.func.isRequired,
    onSelectHomeLocation: PropTypes.func.isRequired,
    onSelectHomeLocationFinished: PropTypes.func.isRequired,
    tool: FmPropTypes.tool,
    nlcOpacity: PropTypes.number.isRequired,
    touristOverlayOpacity: PropTypes.number.isRequired,
    cycloOverlayOpacity: PropTypes.number.isRequired,
    zoom: PropTypes.number,
    expertMode: PropTypes.bool.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      homeLocationCssClasses: '',
      tileFormat: props.tileFormat,
      homeLocation: props.homeLocation,
      nlcOpacity: props.nlcOpacity,
      touristOverlayOpacity: props.touristOverlayOpacity,
      cycloOverlayOpacity: props.cycloOverlayOpacity,
      expertMode: props.expertMode,
    };
  }

  componentWillMount() {
    mapEventEmitter.on('mapClick', this.onHomeLocationSelected);
  }

  componentWillUnmount() {
    mapEventEmitter.removeListener('mapClick', this.onHomeLocationSelected);
  }

  onHomeLocationSelected = (lat, lon) => {
    this.setState({ homeLocation: { lat, lon }, homeLocationCssClasses: 'animated flash' }); // via animate.css
    this.props.onSelectHomeLocationFinished();
  }

  handleSave = () => {
    this.props.onSave(this.state.tileFormat,
      this.state.homeLocation,
      this.state.nlcOpacity,
      this.state.touristOverlayOpacity,
      this.state.cycloOverlayOpacity,
      this.state.expertMode,
    );
  }

  render() {
    const { onClosePopup, onSelectHomeLocation, tool, zoom } = this.props;
    const { homeLocation, homeLocationCssClasses } = this.state;
    const nlcOverlayIsNotVisible = zoom < 14;

    const userMadeChanges = ['tileFormat', 'homeLocation', 'nlcOpacity', 'touristOverlayOpacity', 'cycloOverlayOpacity', 'expertMode']
      .some(prop => this.state[prop] !== this.props[prop]);

    let homeLocationInfo = 'neurčená';
    if (homeLocation.lat && homeLocation.lon) {
      homeLocationInfo = `${formatGpsCoord(homeLocation.lat, 'SN')} ${formatGpsCoord(homeLocation.lon, 'WE')}`;
    }

    return (
      <Modal show={tool !== 'select-home-location'} onHide={onClosePopup}>
        <Modal.Header closeButton>
          <Modal.Title>Nastavenia</Modal.Title>
        </Modal.Header>
        <Modal.Body>

            Formát dlaždíc:

            <ButtonGroup>
              <Button
                active={this.state.tileFormat === 'png'}
                onClick={() => this.setState({ tileFormat: 'png' })}
              >
                PNG
              </Button>
              <Button
                active={this.state.tileFormat === 'jpeg'}
                onClick={() => this.setState({ tileFormat: 'jpeg' })}
              >
                JPG
              </Button>
            </ButtonGroup>

          <Alert>
            Mapové dlaždice vyzerajú lepšie v PNG formáte, ale sú asi 4x väčšie než JPG dlaždice.
            Pri pomalom internete preto odporúčame zvoliť JPG.
          </Alert>

          Domovská poloha: {homeLocationInfo}

          <Button onClick={() => onSelectHomeLocation()}>
            <FontAwesomeIcon icon="crosshairs" /> Vybrať na mape
          </Button>

          Viditeľnosť vrstvy Lesné cesty NLC: {this.state.nlcOpacity.toFixed(1) * 100}%
          <Slider
            value={this.state.nlcOpacity}
            min={0.1}
            max={1.0}
            step={0.1}
            tooltip={false}
            onChange={newOpacity => this.setState({ nlcOpacity: newOpacity })}
          />
          {nlcOverlayIsNotVisible &&
            <Alert>
              NLC vrstva sa zobrazuje až pri detailnejšom priblížení (od zoom úrovne 14).
            </Alert>
          }

            Expertný mód:

            <ButtonGroup>
              <Button
                active={!this.state.expertMode}
                onClick={() => this.setState({ expertMode: false })}
              >
                Vypnutý
              </Button>
              <Button
                active={this.state.expertMode}
                onClick={() => this.setState({ expertMode: true })}
              >
                Zapnutý
              </Button>
            </ButtonGroup>

          {!this.state.expertMode &&
            <Alert>
              V expertnom móde sú dostupné nástroje pre pokročilých používateľov.
            </Alert>
          }
          {this.state.expertMode &&

                Viditeľnosť vrstvy Turistické trasy: {(this.state.touristOverlayOpacity).toFixed(1) * 100}%
              <Slider
                value={this.state.touristOverlayOpacity}
                min={0.1}
                max={1.0}
                step={0.1}
                tooltip={false}
                onChange={newOpacity => this.setState({ touristOverlayOpacity: newOpacity })}
              />

          }
          {this.state.expertMode &&

                Viditeľnosť vrstvy Cyklotrasy: {(this.state.cycloOverlayOpacity).toFixed(1) * 100}%
              <Slider
                value={this.state.cycloOverlayOpacity}
                min={0.1}
                max={1.0}
                step={0.1}
                tooltip={false}
                onChange={newOpacity => this.setState({ cycloOverlayOpacity: newOpacity })}
              />

          }
        </Modal.Body>
        <Modal.Footer>
          <Button bsStyle="info" onClick={this.handleSave} disabled={!userMadeChanges}><Glyphicon glyph="floppy-disk" /> Uložiť</Button>
          <Button onClick={onClosePopup}><Glyphicon glyph="remove" /> Zrušiť</Button>
        </Modal.Footer>
      </Modal>
    );
  }
}

export default connect(
  state => ({
    tileFormat: state.map.tileFormat,
    homeLocation: state.main.homeLocation,
    tool: state.main.tool,
    zoom: state.map.zoom,
    nlcOpacity: state.map.overlayOpacity.N,
    touristOverlayOpacity: state.map.overlayOpacity.t,
    cycloOverlayOpacity: state.map.overlayOpacity.c,
    expertMode: state.main.expertMode,
  }),
  dispatch => ({
    onSave(tileFormat, homeLocation, nlcOpacity, touristOverlayOpacity, cycloOverlayOpacity, expertMode) {
      // TODO replace with signle dispatch (for good pratcice)
      dispatch(mapSetTileFormat(tileFormat));
      dispatch(setHomeLocation(homeLocation));
      dispatch(mapSetOverlayOpacity('N', nlcOpacity));
      dispatch(mapSetOverlayOpacity('t', touristOverlayOpacity));
      dispatch(mapSetOverlayOpacity('c', cycloOverlayOpacity));
      dispatch(closePopup());
      dispatch(setExpertMode(expertMode));
      dispatch(toastsAdd({
        collapseKey: 'settings.saved',
        message: 'Zmeny boli uložené.',
        style: 'info',
        timeout: 3000,
      }));
    },
    onClosePopup() {
      dispatch(closePopup());
    },
    onSelectHomeLocation() {
      // TODO replace with signle dispatch (for good pratcice)
      dispatch(setTool('select-home-location'));
      dispatch(setMouseCursorToCrosshair());
    },
    onSelectHomeLocationFinished() {
      // TODO replace with signle dispatch (for good pratcice)
      dispatch(setTool(null));
      dispatch(resetMouseCursor());
    },
  }),
)(Settings);
<|endoftext|>


<commit_before>var info      = require('../package.json');
var path      = require('path');

var clientAppPath = path.join(__dirname, '../');

var devRelativeOutput     = '/assets/';
var prodRelativeOutput    = '/assets/';

var devOutput     = path.join(__dirname, '../../app/assets/javascripts');
var prodOutput    = path.join(__dirname, '../../app/assets/javascripts');

var hotPort = 8080;
var devAssetsUrl = 'https://cs_assets.ngrok.com'

module.exports = {
  title: info.title,
  author: info.author,
  version: info.versions,
  build: Date.now(),

  devRelativeOutput: devRelativeOutput,
  prodRelativeOutput: prodRelativeOutput,

  devOutput: devOutput,
  prodOutput: prodOutput,

  // Used by pageSpeed. This should be the url of your production server
  applicationUrl: 'http://www.example.com',

  // Dev urls
  devAssetsUrl: devAssetsUrl || 'http://localhost:' + hotPort,

  ports: {
    hotPort: hotPort
  },
  assets: {
    paths: {
      all: clientAppPath + 'assets/**/*',
      output: {
        dev: devOutput,
        prod: prodOutput
      }
    }
  },
  fonts: {
    paths: {
      all: [
        clientAppPath + 'fonts/**'
        // './node_modules/bootstrap-sass/assets/fonts/**',
        // './node_modules/font-awesome/fonts/**'
      ],
      output: {
        dev: devOutput,
        prod: prodOutput
      }
    }
  },
  scripts: {
    paths: {
      all: clientAppPath + 'js/**/*.js',
      entries: {
        app: clientAppPath + 'js/app.jsx',
        app_admin: clientAppPath + 'js/app_admin.jsx',
        styles: clientAppPath + 'styles/styles.js'
      },
      output: {
        dev: devOutput,
        prod: prodOutput
      },
      relativeOutput: {
        dev: devRelativeOutput,
        prod: prodRelativeOutput
      }
    }
  },
  styles: {
    autoPrefix: ['last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'],
    paths: {
      all: clientAppPath + 'styles/**/*.scss',

      output: {
        dev: devOutput,
        prod: prodOutput
      }
    },
    sourcemaps: false
  },
  html: {
    paths: {
      all: clientAppPath + 'html/**/*.html',
      ignore: clientAppPath + '!html/**/_*.html',
      output: {
        dev: devOutput,
        prod: prodOutput
      }
    }
  },
  images: {
    paths: {
      all: [clientAppPath + 'images/*', clientAppPath + 'images/**/*.jpg', clientAppPath + 'images/**/*.jpeg', clientAppPath + 'images/**/*.gif'],
      output: {
        dev: devOutput,
        prod: prodOutput
      }
    },
    compression: 3
  }
};
<commit_msg>Added notes<commit_after>var info      = require('../package.json');
var path      = require('path');

var clientAppPath = path.join(__dirname, '../');

var devRelativeOutput     = '/assets/';
var prodRelativeOutput    = '/assets/';

var devOutput     = path.join(__dirname, '../../app/assets/javascripts');
var prodOutput    = path.join(__dirname, '../../app/assets/javascripts');

var hotPort = 8080;
var devAssetsUrl = 'https://cs_assets.ngrok.com' // Set to null to use localhost

module.exports = {
  title: info.title,
  author: info.author,
  version: info.versions,
  build: Date.now(),

  devRelativeOutput: devRelativeOutput,
  prodRelativeOutput: prodRelativeOutput,

  devOutput: devOutput,
  prodOutput: prodOutput,

  // Used by pageSpeed. This should be the url of your production server
  applicationUrl: 'http://www.example.com',

  // Dev urls
  devAssetsUrl: devAssetsUrl || 'http://localhost:' + hotPort,

  ports: {
    hotPort: hotPort
  },
  assets: {
    paths: {
      all: clientAppPath + 'assets/**/*',
      output: {
        dev: devOutput,
        prod: prodOutput
      }
    }
  },
  fonts: {
    paths: {
      all: [
        clientAppPath + 'fonts/**'
        // './node_modules/bootstrap-sass/assets/fonts/**',
        // './node_modules/font-awesome/fonts/**'
      ],
      output: {
        dev: devOutput,
        prod: prodOutput
      }
    }
  },
  scripts: {
    paths: {
      all: clientAppPath + 'js/**/*.js',
      entries: {
        app: clientAppPath + 'js/app.jsx',
        app_admin: clientAppPath + 'js/app_admin.jsx',
        styles: clientAppPath + 'styles/styles.js'
      },
      output: {
        dev: devOutput,
        prod: prodOutput
      },
      relativeOutput: {
        dev: devRelativeOutput,
        prod: prodRelativeOutput
      }
    }
  },
  styles: {
    autoPrefix: ['last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'],
    paths: {
      all: clientAppPath + 'styles/**/*.scss',

      output: {
        dev: devOutput,
        prod: prodOutput
      }
    },
    sourcemaps: false
  },
  html: {
    paths: {
      all: clientAppPath + 'html/**/*.html',
      ignore: clientAppPath + '!html/**/_*.html',
      output: {
        dev: devOutput,
        prod: prodOutput
      }
    }
  },
  images: {
    paths: {
      all: [clientAppPath + 'images/*', clientAppPath + 'images/**/*.jpg', clientAppPath + 'images/**/*.jpeg', clientAppPath + 'images/**/*.gif'],
      output: {
        dev: devOutput,
        prod: prodOutput
      }
    },
    compression: 3
  }
};
<|endoftext|>


<commit_before>(function(window, document) {
  'use strict';

  function Pin(container, options) {
    this.el = document.querySelector(container);
    this.parent = this.el.parentNode;

    this.setOptions(options);

    this.init();
  }

  Pin.prototype.setOptions = function(options) {
    this.options = {
      onPin: (options.onPin) ? options.onPin : function() {},
      onUnpin: (options.onUnpin) ? options.onUnpin : function() {},
      onTouchBottom: (options.onTouchBottom) ? options.onTouchBottom : function() {}
    };
  };

  Pin.prototype.init = function() {
    this.createHelperElement();
    this.bind();
    this.calcPositions();

    // simulates the window scroll, in case the user is already
    // in the middle of the scroll bar when the page loads (default browser action)
    this.onWindowScroll();
  };

  Pin.prototype.createHelperElement = function() {
    this.helperDiv = document.createElement(this.el.tagName);
    setStyle(this.helperDiv, {
      position: getStyle(this.el, 'position'),
      width: getStyle(this.el, 'width'),
      height: getStyle(this.el, 'height'),
      float: getStyle(this.el, 'float'),
      display: 'none',
      visibility: 'hidden'
    });

    this.parent.insertBefore(this.helperDiv, this.el)
  };

  Pin.prototype.showHelperElement = function(show) {
    setStyle(this.helperDiv, {
      display: (show === false) ? 'none' : 'block'
    })
  };

  Pin.prototype.destroy = function() {
    setStyle(this.el);
    this.unbind();
  };

  Pin.prototype.calcPositions = function() {
    // if parent does not have a position
    // sets a position relative, that's because
    // the pinned element gets position absolute
    if(getStyle(this.parent, 'position') === 'static') {
      setStyle(this.parent, {
        position: 'relative'
      });
    }

    this.positions = {
      offset: this.getOffset(this.el),
      parentOffset: this.getParentOffset(),
      stopTop: (this.parent.offsetHeight + this.getOffset(this.parent).top) - this.el.offsetHeight
    };
  };

  Pin.prototype.getOffset = function(element) {
    var de = document.documentElement;
    var box = element.getBoundingClientRect();

    return {
      top: box.top + window.pageYOffset - de.clientTop,
      left: box.left + window.pageXOffset - de.clientLeft
    };
  };

  Pin.prototype.getParentOffset = function() {
    return {
      left: this.el.offsetLeft
    };
  };

  Pin.prototype.bind = function() {
    // need to attach to a seperate variable, since bind
    // creates another wraped function, and I can't unbind
    // it in the destroy method.
    this.reloadBind = this.reload.bind(this);
    this.onWindowScrollBind = this.onWindowScroll.bind(this);

    window.addEventListener('resize', this.reloadBind);
    window.addEventListener('scroll', this.onWindowScrollBind);
  };

  Pin.prototype.unbind = function() {
    window.removeEventListener('resize', this.reloadBind);
    window.removeEventListener('scroll', this.onWindowScrollBind);
  };

  Pin.prototype.reload = function() {
    this.showHelperElement(false);
    setStyle(this.el);
    this.calcPositions();
    this.onWindowScroll();
  };

  /**
  * Lots of things happens on this method
  * It checks the window size, the position of the scroll
  * If the container is touching the bottom and other
  * stuff. That's why it looks like a mess
  * :|
  **/
  Pin.prototype.onWindowScroll = function() {
    var newTop;

    // if the window is smaller then it won't stick
    if(windowIsSmaller(this.el)) return;

    // if the window got to the bottom of the parent element
    // of the container element, it stops the element
    if(this.touchBottom()) return;
    newTop = window.pageYOffset - this.positions.offset.top;

    // some checks to stop unecessary code repetition
    if(newTop > 0 && getStyle(this.el, 'position') === 'fixed') return;
    if(newTop <= 0 && (getStyle(this.el, 'position') === 'relative' || getStyle(this.el, 'position') === 'static')) return;

    if(newTop > 0) {
      setStyle(this.el, {
        position: 'fixed',
        // adds the left and top property, minus the margins,
        // so the element sticks in the same position it was before
        left: toPx(this.positions.offset.left),
        top: 0,
        marginLeft: 0,
        marginTop: 0,
        bottom: ''
      });

      this.options.onPin(this);
      this.showHelperElement();

      return;
    }

    setStyle(this.el);
    this.options.onUnpin(this);
    this.showHelperElement(false);
  };

  Pin.prototype.touchBottom = function() {
    // if the scroll passed the end of the parent
    if(window.pageYOffset > this.positions.stopTop) {
      if(getStyle(this.el, 'position') === 'absolute') return true;

      setStyle(this.el, {
        top: '',
        marginLeft: '',
        bottom: 0,
        position: 'absolute',
        left: toPx(this.positions.parentOffset.left)
      });

      this.options.onTouchBottom(this);
      this.showHelperElement();

      return true;
    }

    return false;
  };

  /**
   * Private functions
   *
   * The functions below are not supposed to be used by the dev
   */

  function setStyle(el, properties) {
    if(!properties) {
      el.removeAttribute('style');
      return;
    }

    for(var property in properties) {
      el.style[property] = properties[property];
    }
  }

  function getStyle(el, property) {
    return (property) ? window.getComputedStyle(el)[property] : window.getComputedStyle(el);
  }

  function toPx(n) {
    return n + 'px';
  }

  function isMobile() {
    return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
  }

  function windowIsSmaller(el) {
    return window.innerHeight < el.offsetHeight;
  }

  window.Pin = Pin;

} (window, document));
<commit_msg>fix jshint<commit_after>(function(window, document) {
  'use strict';

  function Pin(container, options) {
    this.el = document.querySelector(container);
    this.parent = this.el.parentNode;

    this.setOptions(options);

    this.init();
  }

  Pin.prototype.setOptions = function(options) {
    this.options = {
      onPin: (options.onPin) ? options.onPin : function() {},
      onUnpin: (options.onUnpin) ? options.onUnpin : function() {},
      onTouchBottom: (options.onTouchBottom) ? options.onTouchBottom : function() {}
    };
  };

  Pin.prototype.init = function() {
    this.createHelperElement();
    this.bind();
    this.calcPositions();

    // simulates the window scroll, in case the user is already
    // in the middle of the scroll bar when the page loads (default browser action)
    this.onWindowScroll();
  };

  Pin.prototype.createHelperElement = function() {
    this.helperDiv = document.createElement(this.el.tagName);
    setStyle(this.helperDiv, {
      position: getStyle(this.el, 'position'),
      width: getStyle(this.el, 'width'),
      height: getStyle(this.el, 'height'),
      float: getStyle(this.el, 'float'),
      display: 'none',
      visibility: 'hidden'
    });

    this.parent.insertBefore(this.helperDiv, this.el);
  };

  Pin.prototype.showHelperElement = function(show) {
    setStyle(this.helperDiv, {
      display: (show === false) ? 'none' : 'block'
    });
  };

  Pin.prototype.destroy = function() {
    setStyle(this.el);
    this.unbind();
  };

  Pin.prototype.calcPositions = function() {
    // if parent does not have a position
    // sets a position relative, that's because
    // the pinned element gets position absolute
    if(getStyle(this.parent, 'position') === 'static') {
      setStyle(this.parent, {
        position: 'relative'
      });
    }

    this.positions = {
      offset: this.getOffset(this.el),
      parentOffset: this.getParentOffset(),
      stopTop: (this.parent.offsetHeight + this.getOffset(this.parent).top) - this.el.offsetHeight
    };
  };

  Pin.prototype.getOffset = function(element) {
    var de = document.documentElement;
    var box = element.getBoundingClientRect();

    return {
      top: box.top + window.pageYOffset - de.clientTop,
      left: box.left + window.pageXOffset - de.clientLeft
    };
  };

  Pin.prototype.getParentOffset = function() {
    return {
      left: this.el.offsetLeft
    };
  };

  Pin.prototype.bind = function() {
    // need to attach to a seperate variable, since bind
    // creates another wraped function, and I can't unbind
    // it in the destroy method.
    this.reloadBind = this.reload.bind(this);
    this.onWindowScrollBind = this.onWindowScroll.bind(this);

    window.addEventListener('resize', this.reloadBind);
    window.addEventListener('scroll', this.onWindowScrollBind);
  };

  Pin.prototype.unbind = function() {
    window.removeEventListener('resize', this.reloadBind);
    window.removeEventListener('scroll', this.onWindowScrollBind);
  };

  Pin.prototype.reload = function() {
    this.showHelperElement(false);
    setStyle(this.el);
    this.calcPositions();
    this.onWindowScroll();
  };

  /**
  * Lots of things happens on this method
  * It checks the window size, the position of the scroll
  * If the container is touching the bottom and other
  * stuff. That's why it looks like a mess
  * :|
  **/
  Pin.prototype.onWindowScroll = function() {
    var newTop;

    // if the window is smaller then it won't stick
    if(windowIsSmaller(this.el)) return;

    // if the window got to the bottom of the parent element
    // of the container element, it stops the element
    if(this.touchBottom()) return;
    newTop = window.pageYOffset - this.positions.offset.top;

    // some checks to stop unecessary code repetition
    if(newTop > 0 && getStyle(this.el, 'position') === 'fixed') return;
    if(newTop <= 0 && (getStyle(this.el, 'position') === 'relative' || getStyle(this.el, 'position') === 'static')) return;

    if(newTop > 0) {
      setStyle(this.el, {
        position: 'fixed',
        // adds the left and top property, minus the margins,
        // so the element sticks in the same position it was before
        left: toPx(this.positions.offset.left),
        top: 0,
        marginLeft: 0,
        marginTop: 0,
        bottom: ''
      });

      this.options.onPin(this);
      this.showHelperElement();

      return;
    }

    setStyle(this.el);
    this.options.onUnpin(this);
    this.showHelperElement(false);
  };

  Pin.prototype.touchBottom = function() {
    // if the scroll passed the end of the parent
    if(window.pageYOffset > this.positions.stopTop) {
      if(getStyle(this.el, 'position') === 'absolute') return true;

      setStyle(this.el, {
        top: '',
        marginLeft: '',
        bottom: 0,
        position: 'absolute',
        left: toPx(this.positions.parentOffset.left)
      });

      this.options.onTouchBottom(this);
      this.showHelperElement();

      return true;
    }

    return false;
  };

  /**
   * Private functions
   *
   * The functions below are not supposed to be used by the dev
   */

  function setStyle(el, properties) {
    if(!properties) {
      el.removeAttribute('style');
      return;
    }

    for(var property in properties) {
      el.style[property] = properties[property];
    }
  }

  function getStyle(el, property) {
    return (property) ? window.getComputedStyle(el)[property] : window.getComputedStyle(el);
  }

  function toPx(n) {
    return n + 'px';
  }

  function isMobile() {
    return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
  }

  function windowIsSmaller(el) {
    return window.innerHeight < el.offsetHeight;
  }

  window.Pin = Pin;

} (window, document));
<|endoftext|>


<commit_before>const AbstractModule = require('./AbstractModule');
const Pg = 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, \'\') = \'\';';
var resetDraft_1 = `SELECT Drafter, Role FROM Drafts WHERE Channel = $1;`;
var resetDraft_2 = `UPDATE Drafts SET Channel = '', Role = '' WHERE Channel = $1;`;

class DraftScheduler extends AbstractModule {
  constructor() {
    super();

    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 => {
                        var gm = msg.guild.member(drafter);
                        gm.addRole(role, `You're drafting ${row.name}`);
                        callback(null, gm.nickname);
                      }),
                      (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}`);
                  });
                });
              }));
          });
        }
      },
      {
        'key': 'message',
        'callback': message => {
          var r = /!reset/i.exec(message.content);
          if (!r) return;

          this.pgClient.query(resetDraft_1, [message.channel.id], (err, res) => {
            var drafter = res.rows[0].drafter;
            var role = res.rows[0].role;

            var author = message.guild.members.get(message.author.id);
            if (author.permissions.bitfield & Permissions.FLAGS.ADMINISTRATOR || message.author.id == drafter) {
              return;
              this.pgClient.query(resetDraft_2, [message.channel.id], console.log);
              message.guild.roles.get(role).delete('Cleaning up draft');
              message.channel.delete('Cleaning Up Draft');
            }
          });
        }
      },];
  }

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

module.exports = DraftScheduler;<commit_msg>Added automatic Draft Proposal updates<commit_after>const AbstractModule = require('./AbstractModule');
const Pg = 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, \'\') = \'\';';
var resetDraft_1 = `SELECT Drafter, Role FROM Drafts WHERE Channel = $1;`;
var resetDraft_2 = `UPDATE Drafts SET Channel = '', Role = '' WHERE Channel = $1;`;
var getDraft = 'SELECT Guild, oChannel, Msg, Draft_Key FROM Drafts WHERE Name = $1 AND Drafter = $2;';

class DraftScheduler extends AbstractModule {
  constructor() {
    super();

    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 => {
                        var gm = msg.guild.member(drafter);
                        gm.addRole(role, `You're drafting ${row.name}`);
                        callback(null, gm.nickname);
                      }),
                      (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 your 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}`);
                  });
                });
              }));
          });
        }
      },
      {
        'key': 'message',
        'callback': message => {
          var r = /!reset/i.exec(message.content);
          if (!r) return;

          this.pgClient.query(resetDraft_1, [message.channel.id], (err, res) => {
            var drafter = res.rows[0].drafter;
            var role = res.rows[0].role;

            var author = message.guild.members.get(message.author.id);
            if (author.permissions.bitfield & Permissions.FLAGS.ADMINISTRATOR || message.author.id == drafter) {
              return;
              this.pgClient.query(resetDraft_2, [message.channel.id], console.log);
              message.guild.roles.get(role).delete('Cleaning up draft');
              message.channel.delete('Cleaning Up Draft');
            }
          });
        }
      },
      {
        'key': 'messageUpdate',
        'callback': (oldMessage, newMessage) => {
          var o = draft.exec(oldMessage.content);
          if (!o) return;

          this.pgClient.query(getDraft, [o[1], oldMessage.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) => {
                    oldMessage.channel.send(`Hey ${oldMessage.author.username}, I've updated your 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) => {
        this.updateDrafts(); response.send('Checking if there\'s drafts to make...');
      }
    };
  }
}

module.exports = DraftScheduler;<|endoftext|>


<commit_before>
const RG = require('../src/rg');
RG.Component = require('../src/component');

const DebugGame = function(fact, parser) {
    const _fact = fact;
    const _parser = parser;

    this.create = function(obj, game, player) {
        const sqrPerItem = obj.sqrPerItem;
        obj.cols = 100;
        obj.rows = 100;
        const level = _fact.createLastBattle(game, obj);

        const spirit = new RG.Actor.Spirit('Wolf spirit');
        spirit.get('Stats').setStrength(500);
        level.addActor(spirit, 2, 1);

        const gem = new RG.Item.SpiritGem('Lesser gem');
        level.addItem(gem);

        const pickaxe = _parser.createActualObj('items', 'Pick-axe');
        level.addItem(pickaxe, 2, 2);

        const poison = _parser.createActualObj('items',
            'Potion of frost poison');
        poison.count = 5;
        level.addItem(poison, 2, 2);
        const curePoison = _parser.createActualObj('items',
            'Potion of cure poison');
        level.addItem(curePoison, 3, 2);

        const rifle = _parser.createActualObj('items', 'Rifle');
        const ammo = _parser.createActualObj('items', 'Steel bullet');
        ammo.setCount(100);
        level.addItem(rifle, 1, 1);
        level.addItem(ammo, 1, 1);

        // Test for shops
        const keeper = _parser.createActualObj('actors', 'shopkeeper');
        const gold = new RG.Item.GoldCoin();
        gold.count = 50;
        keeper.getInvEq().addItem(gold);
        level.addActor(keeper, 2, 2);
        const shopElem = new RG.Element.Shop();
        const shopCell = level.getMap().getCell(3, 3);
        shopCell.setProp('elements', shopElem);
        const soldItem = _parser.createActualObj('items', 'Ruby glass sword');
        soldItem.add('Unpaid', new RG.Component.Unpaid());
        shopCell.setProp('items', soldItem);
        shopElem.setShopkeeper(keeper);

        const numFree = level.getMap().getFree().length;
        const itemsPerLevel = Math.round(numFree / sqrPerItem);

        const itemConf = {
            itemsPerLevel,
            func: (item) => (item.value <= 2500),
            maxValue: 2500,
            food: () => true,
            gold: () => false
        };
        _fact.addNRandItems(level, _parser, itemConf);

        const cols = level.getMap().cols;
        const rows = level.getMap().rows;

        const boss = _parser.createActor('Thabba, Son of Ice');
        level.addActor(boss, cols - 2, rows - 2);

        const cryomancer = _parser.createActor('Cryomancer');
        level.addActor(cryomancer, 1, rows - 2);

        const spiritPot = _parser.createActualObj(
            'items', 'Potion of spirit form');
        player.getInvEq().addItem(spiritPot);

        const potStr = _parser.createItem('Potion of strength');
        player.getInvEq().addItem(potStr);

        // BladeMaster components
        player.add('Attacker', new RG.Component.Attacker());
        player.add('Defender', new RG.Component.Defender());
        player.add('MasterEquipper', new RG.Component.MasterEquipper());
        player.add('BiDirStrike', new RG.Component.BiDirStrike());
        player.add('CounterAttack', new RG.Component.BiDirStrike());

        // Marksman components
        player.add(new RG.Component.ThroughShot());

        const winCond = new RG.Game.WinCondition('Kill a keeper');
        winCond.addActorKilled(keeper);

        game.addPlayer(player);

        const eq = player.getInvEq().getEquipment();
        eq.addSlot('spiritgem', new RG.Inv.EquipSlot(eq, 'spiritgem'));
        const gem1 = _parser.createItem('Lesser spirit gem');
        const gem2 = _parser.createItem('Greater spirit gem');
        player.getInvEq().addItem(gem1);
        player.getInvEq().addItem(gem2);
        player.add(new RG.Component.SpiritItemCrafter());

        const exploreElem = new RG.Element.Exploration();
        exploreElem.setExp(100);
        level.addElement(exploreElem, 1, 20);

        const trainer = this.createTrainer();
        level.addActor(trainer, 1, 2);

        const coins = new RG.Item.GoldCoin();
        coins.count = 600;
        player.getInvEq().addItem(coins);

        if (!player.getBook()) {
            const spellbook = new RG.Spell.SpellBook(player);
            player.setBook(spellbook);
            RG.Spell.addAllSpells(this._spellbook);
            player.add(new RG.Component.SpellPower());
            player.get('SpellPower').setPP(100);
        }

        const vActor = new RG.Actor.Virtual('spawner');
        const spawnBrain = new RG.Brain.Spawner(vActor);
        spawnBrain.setConstraint({op: 'lt', prop: 'danger', value: 10});
        vActor.setBrain(spawnBrain);
        level.addVirtualProp(RG.TYPE_ACTOR, vActor);

        const fire = _parser.createActor('Fire');
        level.addActor(fire, 7, 1);

        return game;
    };

    this.createTrainer = function() {
        const human = _parser.createActor('fighter');
        human.setName('Old trainer');
        const trainComp = new RG.Component.Trainer();
        trainComp.getChatObj().setTrainer(human);
        human.add(trainComp);
        return human;
    };
};

module.exports = DebugGame;
<commit_msg>Added fading component for testing.<commit_after>
const RG = require('../src/rg');
RG.Component = require('../src/component');

const DebugGame = function(fact, parser) {
    const _fact = fact;
    const _parser = parser;

    this.create = function(obj, game, player) {
        const sqrPerItem = obj.sqrPerItem;
        obj.cols = 100;
        obj.rows = 100;
        const level = _fact.createLastBattle(game, obj);

        const spirit = new RG.Actor.Spirit('Wolf spirit');
        spirit.get('Stats').setStrength(500);
        level.addActor(spirit, 2, 1);

        const gem = new RG.Item.SpiritGem('Lesser gem');
        level.addItem(gem);

        const pickaxe = _parser.createActualObj('items', 'Pick-axe');
        level.addItem(pickaxe, 2, 2);

        const poison = _parser.createActualObj('items',
            'Potion of frost poison');
        poison.count = 5;
        level.addItem(poison, 2, 2);
        const curePoison = _parser.createActualObj('items',
            'Potion of cure poison');
        level.addItem(curePoison, 3, 2);

        const rifle = _parser.createActualObj('items', 'Rifle');
        const ammo = _parser.createActualObj('items', 'Steel bullet');
        ammo.setCount(100);
        level.addItem(rifle, 1, 1);
        level.addItem(ammo, 1, 1);

        // Test for shops
        const keeper = _parser.createActualObj('actors', 'shopkeeper');
        const gold = new RG.Item.GoldCoin();
        gold.count = 50;
        keeper.getInvEq().addItem(gold);
        level.addActor(keeper, 2, 2);
        const shopElem = new RG.Element.Shop();
        const shopCell = level.getMap().getCell(3, 3);
        shopCell.setProp('elements', shopElem);
        const soldItem = _parser.createActualObj('items', 'Ruby glass sword');
        soldItem.add('Unpaid', new RG.Component.Unpaid());
        shopCell.setProp('items', soldItem);
        shopElem.setShopkeeper(keeper);

        const numFree = level.getMap().getFree().length;
        const itemsPerLevel = Math.round(numFree / sqrPerItem);

        const itemConf = {
            itemsPerLevel,
            func: (item) => (item.value <= 2500),
            maxValue: 2500,
            food: () => true,
            gold: () => false
        };
        _fact.addNRandItems(level, _parser, itemConf);

        const cols = level.getMap().cols;
        const rows = level.getMap().rows;

        const boss = _parser.createActor('Thabba, Son of Ice');
        level.addActor(boss, cols - 2, rows - 2);

        const cryomancer = _parser.createActor('Cryomancer');
        level.addActor(cryomancer, 1, rows - 2);

        const spiritPot = _parser.createActualObj(
            'items', 'Potion of spirit form');
        player.getInvEq().addItem(spiritPot);

        const potStr = _parser.createItem('Potion of strength');
        player.getInvEq().addItem(potStr);

        // BladeMaster components
        player.add('Attacker', new RG.Component.Attacker());
        player.add('Defender', new RG.Component.Defender());
        player.add('MasterEquipper', new RG.Component.MasterEquipper());
        player.add('BiDirStrike', new RG.Component.BiDirStrike());
        player.add('CounterAttack', new RG.Component.BiDirStrike());

        // Marksman components
        player.add(new RG.Component.ThroughShot());

        const winCond = new RG.Game.WinCondition('Kill a keeper');
        winCond.addActorKilled(keeper);

        game.addPlayer(player);

        const eq = player.getInvEq().getEquipment();
        eq.addSlot('spiritgem', new RG.Inv.EquipSlot(eq, 'spiritgem'));
        const gem1 = _parser.createItem('Lesser spirit gem');
        const gem2 = _parser.createItem('Greater spirit gem');
        player.getInvEq().addItem(gem1);
        player.getInvEq().addItem(gem2);
        player.add(new RG.Component.SpiritItemCrafter());

        const exploreElem = new RG.Element.Exploration();
        exploreElem.setExp(100);
        level.addElement(exploreElem, 1, 20);

        const trainer = this.createTrainer();
        level.addActor(trainer, 1, 2);

        const coins = new RG.Item.GoldCoin();
        coins.count = 600;
        player.getInvEq().addItem(coins);

        if (!player.getBook()) {
            const spellbook = new RG.Spell.SpellBook(player);
            player.setBook(spellbook);
            RG.Spell.addAllSpells(this._spellbook);
            player.add(new RG.Component.SpellPower());
            player.get('SpellPower').setPP(100);
        }

        const vActor = new RG.Actor.Virtual('spawner');
        const spawnBrain = new RG.Brain.Spawner(vActor);
        spawnBrain.setConstraint({op: 'lt', prop: 'danger', value: 10});
        vActor.setBrain(spawnBrain);
        level.addVirtualProp(RG.TYPE_ACTOR, vActor);

        const fire = _parser.createActor('Fire');
        const fadingComp = new RG.Component.Fading();
        fadingComp.setDuration(20);
        fire.add(fadingComp);
        level.addActor(fire, 7, 1);

        return game;
    };

    this.createTrainer = function() {
        const human = _parser.createActor('fighter');
        human.setName('Old trainer');
        const trainComp = new RG.Component.Trainer();
        trainComp.getChatObj().setTrainer(human);
        human.add(trainComp);
        return human;
    };
};

module.exports = DebugGame;
<|endoftext|>