<commit_before>const Eris = require('eris');
const fs = require('fs');
const moment = require('moment');
const humanizeDuration = require('humanize-duration');
const config = require('../config');
const Queue = require('./queue');
const utils = require('./utils');
const blocked = require('./blocked');
const threads = require('./threads');
const logs = require('./logs');
const attachments = require('./attachments');
const webserver = require('./webserver');
const greeting = require('./greeting');

const bot = new Eris.CommandClient(config.token, {}, {
  prefix: config.prefix || '!',
  ignoreSelf: true,
  ignoreBots: true,
  defaultHelpCommand: false,
});

const restBot = new Eris.Client(`Bot ${config.token}`, {
  restMode: true,
});

const messageQueue = new Queue();

bot.on('ready', () => {
  bot.editStatus(null, {name: config.status || 'Message me for help'});
  console.log('Bot started, listening to DMs');
});

restBot.on('ready', () => {
  console.log('Rest client ready');
});

function formatAttachment(attachment) {
  let filesize = attachment.size || 0;
  filesize /= 1024;

  return attachments.getUrl(attachment.id, attachment.filename).then(attachmentUrl => {
    return `**Attachment:** ${attachment.filename} (${filesize.toFixed(1)}KB)\n${attachmentUrl}`;
  });
}

function formatUserDM(msg) {
  let content = msg.content;

  // Get a local URL for all attachments so we don't rely on discord's servers (which delete attachments when the channel/DM thread is deleted)
  const attachmentFormatPromise = msg.attachments.map(formatAttachment);
  return Promise.all(attachmentFormatPromise).then(formattedAttachments => {
    formattedAttachments.forEach(str => {
      content += `\n\n${str}`;
    });

    return content;
  });
}

// "Forward all messages not starting in prefix"
if (config.alwaysReply) {
  bot.on('messageCreate', msg => {
    if (! msg.channel.guild) return;
    if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
    if (! msg.member.permission.has('manageRoles')) return;
    if (msg.author.bot) return;
    if (msg.content[0] == bot.commandOptions.prefix) return;

    reply(msg, msg.content.trim(), config.alwaysReplyAnon || false);
  });
}

// "Bot was mentioned in #general-discussion"
bot.on('messageCreate', msg => {
  if (msg.author.id === bot.user.id) return;

  if (msg.mentions.some(user => user.id === bot.user.id)) {
    // If the person who mentioned the modmail bot is on the modmail server, don't ping about it
    if (utils.getModmailGuild(bot).members.get(msg.author.id)) return;

    blocked.isBlocked(msg.author.id).then(isBlocked => {
      if (isBlocked) return;

      bot.createMessage(utils.getModmailGuild(bot).id, {
        content: `@here Bot mentioned in ${msg.channel.mention} by **${msg.author.username}#${msg.author.discriminator}**: "${msg.cleanContent}"`,
        disableEveryone: false,
      });
    });
  }
});

// When we get a private message, forward the contents to the corresponding modmail thread
bot.on('messageCreate', (msg) => {
  if (! (msg.channel instanceof Eris.PrivateChannel)) return;
  if (msg.author.id === bot.user.id) return;

  blocked.isBlocked(msg.author.id).then(isBlocked => {
    if (isBlocked) return;

    // Download and save copies of attachments in the background
    attachments.saveAttachmentsInMessage(msg);

    let thread, userLogs;

    // Private message handling is queued so e.g. multiple message in quick succession don't result in multiple channels aren't created
    messageQueue.add(() => {
      return threads.getForUser(bot, msg.author)
        .then(userThread => {
          thread = userThread;
          return logs.getLogsByUserId(msg.author.id);
        })
        .then(foundUserLogs => {
          userLogs = foundUserLogs;
          return formatUserDM(msg);
        })
        .then(content => {
          // If the thread does not exist and could not be created, send a warning about this to all mods so they can DM the user directly instead
          if (! thread) {
            let warningMessage = `
@here Error creating modmail thread for ${msg.author.username}#${msg.author.discriminator} (${msg.author.id})!

Here's what their message contained:
\`\`\`${content}\`\`\`
          `.trim();

            bot.createMessage(utils.getModmailGuild(bot).id, {
              content: `@here Error creating modmail thread for ${msg.author.username}#${msg.author.discriminator} (${msg.author.id})!`,
              disableEveryone: false,
            });

            return;
          }

          let threadInitDonePromise = Promise.resolve();

          // If the thread was just created, do some extra stuff
          if (thread._wasCreated) {
            const mainGuild = utils.getMainGuild(restBot);
            const memberPromise = (mainGuild ? mainGuild.getRESTMember(msg.author.id) : Promise.resolve());

            threadInitDonePromise = memberPromise
              .catch(err => {
                console.log(`Member ${msg.author.id} not found in main guild ${config.mainGuildId}`);
                console.error(String(err));
              })
              .then(member => {
                let mainGuildNickname = null;
                if (member && member.nick) mainGuildNickname = member.nick;
                else if (member && member.user) mainGuildNickname = member.user.username;
                else if (member == null) mainGuildNickname = 'NOT ON SERVER';

                if (mainGuildNickname == null) mainGuildNickname = 'UNKNOWN';

                const accountAge = humanizeDuration(Date.now() - msg.author.createdAt, {largest: 2});
                const infoHeader = `ACCOUNT AGE **${accountAge}**, ID **${msg.author.id}**, NICKNAME **${mainGuildNickname}**, LOGS **${userLogs.length}**\n-------------------------------`;

                bot.createMessage(thread.channelId, infoHeader);
              });

            // Ping mods of the new thread
            bot.createMessage(utils.getModmailGuild(bot).id, {
              content: `@here New modmail thread: <#${thread.channelId}>`,
              disableEveryone: false,
            });

            // Send an automatic reply to the user informing them of the successfully created modmail thread
            msg.channel.createMessage(config.responseMessage || "Thank you for your message! Our mod team will reply to you here as soon as possible.").then(null, (err) => {
              bot.createMessage(utils.getModmailGuild(bot).id, {
                content: `There is an issue sending messages to ${msg.author.username}#${msg.author.discriminator} (id ${msg.author.id}); consider messaging manually`
              });
            });
          }

          threadInitDonePromise.then(() => {
            const timestamp = utils.getTimestamp();
            bot.createMessage(thread.channelId, `[${timestamp}] « **${msg.author.username}#${msg.author.discriminator}:** ${content}`);
          });
        });
    });
  });
});

// Edits in DMs
bot.on('messageUpdate', (msg, oldMessage) => {
  if (! (msg.channel instanceof Eris.PrivateChannel)) return;
  if (msg.author.id === bot.user.id) return;

  blocked.isBlocked(msg.author.id).then(isBlocked => {
    if (isBlocked) return;

    let oldContent = oldMessage.content;
    const newContent = msg.content;

    if (oldContent == null) oldContent = '*Unavailable due to bot restart*';

    threads.getForUser(bot, msg.author).then(thread => {
      if (! thread) return;

      const editMessage = utils.disableLinkPreviews(`**The user edited their message:**\n**Before:** ${oldContent}\n**After:** ${newContent}`);

      bot.createMessage(thread.channelId, editMessage);
    });
  });
});

function reply(msg, text, anonymous = false) {
  threads.getByChannelId(msg.channel.id).then(thread => {
    if (! thread) return;

    attachments.saveAttachmentsInMessage(msg).then(() => {
      bot.getDMChannel(thread.userId).then(dmChannel => {
        let modUsername, logModUsername;
        const mainRole = utils.getMainRole(msg.member);

        if (anonymous) {
          modUsername = (mainRole ? mainRole.name : 'Moderator');
          logModUsername = `(Anonymous) (${msg.author.username}) ${mainRole ? mainRole.name : 'Moderator'}`;
        } else {
          modUsername = (mainRole ? `(${mainRole.name}) ${msg.author.username}` : msg.author.username);
          logModUsername = modUsername;
        }

        let content = `**${modUsername}:** ${text}`;
        let logContent = `**${logModUsername}:** ${text}`;

        function sendMessage(file, attachmentUrl) {
          dmChannel.createMessage(content, file).then(() => {
            if (attachmentUrl) {
              content += `\n\n**Attachment:** ${attachmentUrl}`;
              logContent += `\n\n**Attachment:** ${attachmentUrl}`;
            }

            // Show the message in the modmail thread as well
            const timestamp = utils.getTimestamp();
            msg.channel.createMessage(`[${timestamp}] » ${logContent}`);
          }, (err) => {
            if (err.resp && err.resp.statusCode === 403) {
              msg.channel.createMessage(`Could not send reply; the user has likely blocked the bot`);
            } else if (err.resp) {
              msg.channel.createMessage(`Could not send reply; error code ${err.resp.statusCode}`);
            } else {
              msg.channel.createMessage(`Could not send reply: ${err.toString()}`);
            }
          });

          msg.delete();
        };

        // If the reply has an attachment, relay it as is
        if (msg.attachments.length > 0) {
          fs.readFile(attachments.getPath(msg.attachments[0].id), (err, data) => {
            const file = {file: data, name: msg.attachments[0].filename};

            attachments.getUrl(msg.attachments[0].id, msg.attachments[0].filename).then(attachmentUrl => {
              sendMessage(file, attachmentUrl);
            });
          });
        } else {
          sendMessage();
        }
      });
    });
  });
}

// Mods can reply to modmail threads using !r or !reply
// These messages get relayed back to the DM thread between the bot and the user
bot.registerCommand('reply', (msg, args) => {
  if (! msg.channel.guild) return;
  if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  const text = args.join(' ').trim();
  reply(msg, text, false);
});

bot.registerCommandAlias('r', 'reply');

// Anonymous replies only show the role, not the username
bot.registerCommand('anonreply', (msg, args) => {
  if (! msg.channel.guild) return;
  if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  const text = args.join(' ').trim();
  reply(msg, text, true);
});

bot.registerCommandAlias('ar', 'anonreply');

bot.registerCommand('close', (msg, args) => {
  if (! msg.channel.guild) return;
  if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  threads.getByChannelId(msg.channel.id).then(thread => {
    if (! thread) return;

    msg.channel.createMessage('Saving logs and closing channel...');
    msg.channel.getMessages(10000).then(messages => {
      const log = messages.reverse().map(msg => {
          const date = moment.utc(msg.timestamp, 'x').format('YYYY-MM-DD HH:mm:ss');
          return `[${date}] ${msg.author.username}#${msg.author.discriminator}: ${msg.content}`;
        }).join('\n') + '\n';

      logs.getNewLogFile(thread.userId).then(logFilename => {
        logs.saveLogFile(logFilename, log)
          .then(() => logs.getLogFileUrl(logFilename))
          .then(url => {
            const closeMessage = `Modmail thread with ${thread.username} (${thread.userId}) was closed by ${msg.author.mention}
Logs: <${url}>`;

            bot.createMessage(utils.getModmailGuild(bot).id, closeMessage);
            threads.close(thread.channelId).then(() => msg.channel.delete());
          });
      });
    });
  });
});

bot.registerCommand('block', (msg, args) => {
  if (! msg.channel.guild) return;
  if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  function block(userId) {
    blocked.block(userId).then(() => {
      msg.channel.createMessage(`Blocked <@${userId}> (id ${userId}) from modmail`);
    });
  }

  if (args.length > 0) {
    const userId = utils.getUserMention(args.join(' '));
    if (! userId) return;
    block(userId);
  } else {
    // Calling !block without args in a modmail thread blocks the user of that thread
    threads.getByChannelId(msg.channel.id).then(thread => {
      if (! thread) return;
      block(thread.userId);
    });
  }
});

bot.registerCommand('unblock', (msg, args) => {
  if (! msg.channel.guild) return;
  if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  function unblock(userId) {
    blocked.unblock(userId).then(() => {
      msg.channel.createMessage(`Unblocked <@${userId}> (id ${userId}) from modmail`);
    });
  }

  if (args.length > 0) {
    const userId = utils.getUserMention(args.join(' '));
    if (! userId) return;
    unblock(userId);
  } else {
    // Calling !unblock without args in a modmail thread unblocks the user of that thread
    threads.getByChannelId(msg.channel.id).then(thread => {
      if (! thread) return;
      unblock(thread.userId);
    });
  }
});

bot.registerCommand('logs', (msg, args) => {
  if (! msg.channel.guild) return;
  if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  function getLogs(userId) {
    logs.getLogsWithUrlByUserId(userId).then(infos => {
      let message = `**Log files for <@${userId}>:**\n`;

      message += infos.map(info => {
        const formattedDate = moment.utc(info.date, 'YYYY-MM-DD HH:mm:ss').format('MMM Do [at] HH:mm [UTC]');
        return `\`${formattedDate}\`: <${info.url}>`;
      }).join('\n');

      msg.channel.createMessage(message);
    });
  }

  if (args.length > 0) {
    const userId = utils.getUserMention(args.join(' '));
    if (! userId) return;
    getLogs(userId);
  } else {
    // Calling !logs without args in a modmail thread returns the logs of the user of that thread
    threads.getByChannelId(msg.channel.id).then(thread => {
      if (! thread) return;
      getLogs(thread.userId);
    });
  }
});

bot.connect();
restBot.connect();
webserver.run();
greeting.enable(bot);

<commit_msg>Add attachment links to received messages only after the attachments have been saved. Don't show edits that result in identical messages. Post new thread notifications in the thread in question, not the main channel. Remove ping from close messages.<commit_after>const Eris = require('eris');
const fs = require('fs');
const moment = require('moment');
const humanizeDuration = require('humanize-duration');
const config = require('../config');
const Queue = require('./queue');
const utils = require('./utils');
const blocked = require('./blocked');
const threads = require('./threads');
const logs = require('./logs');
const attachments = require('./attachments');
const webserver = require('./webserver');
const greeting = require('./greeting');

const bot = new Eris.CommandClient(config.token, {}, {
  prefix: config.prefix || '!',
  ignoreSelf: true,
  ignoreBots: true,
  defaultHelpCommand: false,
});

const restBot = new Eris.Client(`Bot ${config.token}`, {
  restMode: true,
});

const messageQueue = new Queue();

bot.on('ready', () => {
  bot.editStatus(null, {name: config.status || 'Message me for help'});
  console.log('Bot started, listening to DMs');
});

restBot.on('ready', () => {
  console.log('Rest client ready');
});

function formatAttachment(attachment) {
  let filesize = attachment.size || 0;
  filesize /= 1024;

  return attachments.getUrl(attachment.id, attachment.filename).then(attachmentUrl => {
    return `**Attachment:** ${attachment.filename} (${filesize.toFixed(1)}KB)\n${attachmentUrl}`;
  });
}

// "Forward all messages not starting in prefix"
if (config.alwaysReply) {
  bot.on('messageCreate', msg => {
    if (! msg.channel.guild) return;
    if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
    if (! msg.member.permission.has('manageRoles')) return;
    if (msg.author.bot) return;
    if (msg.content[0] == bot.commandOptions.prefix) return;

    reply(msg, msg.content.trim(), config.alwaysReplyAnon || false);
  });
}

// "Bot was mentioned in #general-discussion"
bot.on('messageCreate', msg => {
  if (msg.author.id === bot.user.id) return;

  if (msg.mentions.some(user => user.id === bot.user.id)) {
    // If the person who mentioned the modmail bot is on the modmail server, don't ping about it
    if (utils.getModmailGuild(bot).members.get(msg.author.id)) return;

    blocked.isBlocked(msg.author.id).then(isBlocked => {
      if (isBlocked) return;

      bot.createMessage(utils.getModmailGuild(bot).id, {
        content: `@here Bot mentioned in ${msg.channel.mention} by **${msg.author.username}#${msg.author.discriminator}**: "${msg.cleanContent}"`,
        disableEveryone: false,
      });
    });
  }
});

// When we get a private message, forward the contents to the corresponding modmail thread
bot.on('messageCreate', (msg) => {
  if (! (msg.channel instanceof Eris.PrivateChannel)) return;
  if (msg.author.id === bot.user.id) return;

  blocked.isBlocked(msg.author.id).then(isBlocked => {
    if (isBlocked) return;

    // Download and save copies of attachments in the background
    const attachmentSavePromise = attachments.saveAttachmentsInMessage(msg);

    let thread, userLogs;

    // Private message handling is queued so e.g. multiple message in quick succession don't result in multiple channels being created
    messageQueue.add(() => {
      return threads.getForUser(bot, msg.author)
        .then(userThread => {
          thread = userThread;
          return logs.getLogsByUserId(msg.author.id);
        })
        .then(foundUserLogs => {
          userLogs = foundUserLogs;
        })
        .then(() => {
          let content = msg.content;

          // If the thread does not exist and could not be created, send a warning about this to all mods so they can DM the user directly instead
          if (! thread) {
            let warningMessage = `
@here Error creating modmail thread for ${msg.author.username}#${msg.author.discriminator} (${msg.author.id})!

Here's what their message contained:
\`\`\`${content}\`\`\`
          `.trim();

            bot.createMessage(utils.getModmailGuild(bot).id, {
              content: `@here Error creating modmail thread for ${msg.author.username}#${msg.author.discriminator} (${msg.author.id})!`,
              disableEveryone: false,
            });

            return;
          }

          let threadInitDonePromise = Promise.resolve();

          // If the thread was just created, do some extra stuff
          if (thread._wasCreated) {
            const mainGuild = utils.getMainGuild(restBot);
            const memberPromise = (mainGuild ? mainGuild.getRESTMember(msg.author.id) : Promise.resolve());

            threadInitDonePromise = memberPromise
              .catch(err => {
                console.log(`Member ${msg.author.id} not found in main guild ${config.mainGuildId}`);
                console.error(String(err));
              })
              .then(member => {
                let mainGuildNickname = null;
                if (member && member.nick) mainGuildNickname = member.nick;
                else if (member && member.user) mainGuildNickname = member.user.username;
                else if (member == null) mainGuildNickname = 'NOT ON SERVER';

                if (mainGuildNickname == null) mainGuildNickname = 'UNKNOWN';

                const accountAge = humanizeDuration(Date.now() - msg.author.createdAt, {largest: 2});
                const infoHeader = `ACCOUNT AGE **${accountAge}**, ID **${msg.author.id}**, NICKNAME **${mainGuildNickname}**, LOGS **${userLogs.length}**\n-------------------------------`;

                return bot.createMessage(thread.channelId, infoHeader);
              })
              .then(() => {
                // Ping mods of the new thread
                bot.createMessage(thread.channelId, {
                  content: `@here New modmail thread (${msg.author.username}#${msg.author.discriminator})`,
                  disableEveryone: false,
                });
              });

            // Send an automatic reply to the user informing them of the successfully created modmail thread
            msg.channel.createMessage(config.responseMessage || "Thank you for your message! Our mod team will reply to you here as soon as possible.").then(null, (err) => {
              bot.createMessage(utils.getModmailGuild(bot).id, {
                content: `There is an issue sending messages to ${msg.author.username}#${msg.author.discriminator} (id ${msg.author.id}); consider messaging manually`
              });
            });
          }

          const attachmentsPendingStr = '\n\n*Attachments pending...*';
          if (msg.attachments.length > 0) content += attachmentsPendingStr;

          threadInitDonePromise.then(() => {
            const timestamp = utils.getTimestamp();
            bot.createMessage(thread.channelId, `[${timestamp}] « **${msg.author.username}#${msg.author.discriminator}:** ${content}`).then(createdMsg => {
              if (msg.attachments.length === 0) return;

              // Once attachments have been saved, add links to them to the message
              attachmentSavePromise.then(() => {
                const attachmentFormatPromises = msg.attachments.map(formatAttachment);
                Promise.all(attachmentFormatPromises).then(formattedAttachments => {
                  let attachmentMsg = '';

                  formattedAttachments.forEach(str => {
                    attachmentMsg += `\n\n${str}`;
                  });

                  createdMsg.edit(createdMsg.content.replace(attachmentsPendingStr, attachmentMsg));
                });
              });
            });
          });
        });
    });
  });
});

// Edits in DMs
bot.on('messageUpdate', (msg, oldMessage) => {
  if (! (msg.channel instanceof Eris.PrivateChannel)) return;
  if (msg.author.id === bot.user.id) return;

  blocked.isBlocked(msg.author.id).then(isBlocked => {
    if (isBlocked) return;

    let oldContent = oldMessage.content;
    const newContent = msg.content;

    if (oldContent == null) oldContent = '*Unavailable due to bot restart*';

    // Ignore bogus edit events with no changes
    if (newContent.trim() === oldContent.trim()) return;

    threads.getForUser(bot, msg.author).then(thread => {
      if (! thread) return;

      const editMessage = utils.disableLinkPreviews(`**The user edited their message:**\n**Before:** ${oldContent}\n**After:** ${newContent}`);

      bot.createMessage(thread.channelId, editMessage);
    });
  });
});

function reply(msg, text, anonymous = false) {
  threads.getByChannelId(msg.channel.id).then(thread => {
    if (! thread) return;

    attachments.saveAttachmentsInMessage(msg).then(() => {
      bot.getDMChannel(thread.userId).then(dmChannel => {
        let modUsername, logModUsername;
        const mainRole = utils.getMainRole(msg.member);

        if (anonymous) {
          modUsername = (mainRole ? mainRole.name : 'Moderator');
          logModUsername = `(Anonymous) (${msg.author.username}) ${mainRole ? mainRole.name : 'Moderator'}`;
        } else {
          modUsername = (mainRole ? `(${mainRole.name}) ${msg.author.username}` : msg.author.username);
          logModUsername = modUsername;
        }

        let content = `**${modUsername}:** ${text}`;
        let logContent = `**${logModUsername}:** ${text}`;

        function sendMessage(file, attachmentUrl) {
          dmChannel.createMessage(content, file).then(() => {
            if (attachmentUrl) {
              content += `\n\n**Attachment:** ${attachmentUrl}`;
              logContent += `\n\n**Attachment:** ${attachmentUrl}`;
            }

            // Show the message in the modmail thread as well
            const timestamp = utils.getTimestamp();
            msg.channel.createMessage(`[${timestamp}] » ${logContent}`);
          }, (err) => {
            if (err.resp && err.resp.statusCode === 403) {
              msg.channel.createMessage(`Could not send reply; the user has likely blocked the bot`);
            } else if (err.resp) {
              msg.channel.createMessage(`Could not send reply; error code ${err.resp.statusCode}`);
            } else {
              msg.channel.createMessage(`Could not send reply: ${err.toString()}`);
            }
          });

          msg.delete();
        };

        // If the reply has an attachment, relay it as is
        if (msg.attachments.length > 0) {
          fs.readFile(attachments.getPath(msg.attachments[0].id), (err, data) => {
            const file = {file: data, name: msg.attachments[0].filename};

            attachments.getUrl(msg.attachments[0].id, msg.attachments[0].filename).then(attachmentUrl => {
              sendMessage(file, attachmentUrl);
            });
          });
        } else {
          sendMessage();
        }
      });
    });
  });
}

// Mods can reply to modmail threads using !r or !reply
// These messages get relayed back to the DM thread between the bot and the user
bot.registerCommand('reply', (msg, args) => {
  if (! msg.channel.guild) return;
  if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  const text = args.join(' ').trim();
  reply(msg, text, false);
});

bot.registerCommandAlias('r', 'reply');

// Anonymous replies only show the role, not the username
bot.registerCommand('anonreply', (msg, args) => {
  if (! msg.channel.guild) return;
  if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  const text = args.join(' ').trim();
  reply(msg, text, true);
});

bot.registerCommandAlias('ar', 'anonreply');

bot.registerCommand('close', (msg, args) => {
  if (! msg.channel.guild) return;
  if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  threads.getByChannelId(msg.channel.id).then(thread => {
    if (! thread) return;

    msg.channel.createMessage('Saving logs and closing channel...');
    msg.channel.getMessages(10000).then(messages => {
      const log = messages.reverse().map(msg => {
          const date = moment.utc(msg.timestamp, 'x').format('YYYY-MM-DD HH:mm:ss');
          return `[${date}] ${msg.author.username}#${msg.author.discriminator}: ${msg.content}`;
        }).join('\n') + '\n';

      logs.getNewLogFile(thread.userId).then(logFilename => {
        logs.saveLogFile(logFilename, log)
          .then(() => logs.getLogFileUrl(logFilename))
          .then(url => {
            const closeMessage = `Modmail thread with ${thread.username} (${thread.userId}) was closed by ${msg.author.username}
Logs: <${url}>`;

            bot.createMessage(utils.getModmailGuild(bot).id, closeMessage);
            threads.close(thread.channelId).then(() => msg.channel.delete());
          });
      });
    });
  });
});

bot.registerCommand('block', (msg, args) => {
  if (! msg.channel.guild) return;
  if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  function block(userId) {
    blocked.block(userId).then(() => {
      msg.channel.createMessage(`Blocked <@${userId}> (id ${userId}) from modmail`);
    });
  }

  if (args.length > 0) {
    const userId = utils.getUserMention(args.join(' '));
    if (! userId) return;
    block(userId);
  } else {
    // Calling !block without args in a modmail thread blocks the user of that thread
    threads.getByChannelId(msg.channel.id).then(thread => {
      if (! thread) return;
      block(thread.userId);
    });
  }
});

bot.registerCommand('unblock', (msg, args) => {
  if (! msg.channel.guild) return;
  if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  function unblock(userId) {
    blocked.unblock(userId).then(() => {
      msg.channel.createMessage(`Unblocked <@${userId}> (id ${userId}) from modmail`);
    });
  }

  if (args.length > 0) {
    const userId = utils.getUserMention(args.join(' '));
    if (! userId) return;
    unblock(userId);
  } else {
    // Calling !unblock without args in a modmail thread unblocks the user of that thread
    threads.getByChannelId(msg.channel.id).then(thread => {
      if (! thread) return;
      unblock(thread.userId);
    });
  }
});

bot.registerCommand('logs', (msg, args) => {
  if (! msg.channel.guild) return;
  if (msg.channel.guild.id !== utils.getModmailGuild(bot).id) return;
  if (! msg.member.permission.has('manageRoles')) return;

  function getLogs(userId) {
    logs.getLogsWithUrlByUserId(userId).then(infos => {
      let message = `**Log files for <@${userId}>:**\n`;

      message += infos.map(info => {
        const formattedDate = moment.utc(info.date, 'YYYY-MM-DD HH:mm:ss').format('MMM Do [at] HH:mm [UTC]');
        return `\`${formattedDate}\`: <${info.url}>`;
      }).join('\n');

      msg.channel.createMessage(message);
    });
  }

  if (args.length > 0) {
    const userId = utils.getUserMention(args.join(' '));
    if (! userId) return;
    getLogs(userId);
  } else {
    // Calling !logs without args in a modmail thread returns the logs of the user of that thread
    threads.getByChannelId(msg.channel.id).then(thread => {
      if (! thread) return;
      getLogs(thread.userId);
    });
  }
});

bot.connect();
restBot.connect();
webserver.run();
greeting.enable(bot);

<|endoftext|>


<commit_before>/*************************************************
 *
 * Functions for file upload fields
 *
 ************************************************/

// Create a file upload field
function fileUploadField(field_id) {
	/* example 2 */
	new AjaxUpload('#upload_button_' + field_id, {
		//action: 'upload.php',
		action: baseUrl + '/file/upload', // I disabled uploads in this example for security reaaons
		data : {},
		name : field_id,
		autoSubmit: true,
		onChange : function(file, ext) {
			oldFile = $('#' + field_id).val();
			if (oldFile != '' && oldFile != 'existing*' && oldFile != '*deleted*' ) {
				if (!confirm('The old file is deleted when uploading a new file. Do you want to continue?')) {
					return false;
				}
			}

			this.setData({
				'field':   field_id,
				'oldFile': oldFile
			});

			// Give feedback to the user
			$('#' + field_id + 'Example').html('Uploading ' + createFileHTML(file));
			$('#' + field_id + 'Delete').hide();

		},
		onComplete : function(file, response) {
			if (response == "") {
				$('#' + field_id).val('');
				$('#' + field_id + 'Example').html('Error uploading ' + createFileHTML(file) + '');
				$('#' + field_id + 'Delete').hide();
			} else {
				$('#' + field_id).val(response);
				$('#' + field_id + 'Example').html('Uploaded ' + createFileHTML(file));
				$('#' + field_id + 'Delete').show();
			}
		}
	});
}

function deleteFile(field_id) {
	$('#' + field_id).val('*deleted*');
	$('#' + field_id + 'Example').html('File deleted');
	$('#' + field_id + 'Delete').hide();
}

function createFileHTML(filename) {
	return '' + filename + '';
}<commit_msg>- added fileupload JS required for the fileuploadfield to work<commit_after>/*************************************************
 *
 * Functions for file upload fields
 *
 ************************************************/

// Create a file upload field
function fileUploadField(field_id) {
	/* example 2 */
	new AjaxUpload('#upload_button_' + field_id, {
		//action: 'upload.php',
		action: baseUrl + '/file/upload/hoihoi', // I disabled uploads in this example for security reaaons
		data : {},
		name : field_id,
		autoSubmit: true,
		onChange : function(file, ext) {
			oldFile = $('#' + field_id).val();
			if (oldFile != '' && oldFile != 'existing*' && oldFile != '*deleted*' ) {
				if (!confirm('The old file is deleted when uploading a new file. Do you want to continue?')) {
					return false;
				}
			}

			this.setData({
				'field':   field_id,
				'oldFile': oldFile
			});

			// Give feedback to the user
			$('#' + field_id + 'Example').html('Uploading ' + createFileHTML(file));
			$('#' + field_id + 'Delete').hide();

		},
		onComplete : function(file, response) {
			if (response == "") {
				$('#' + field_id).val('');
				$('#' + field_id + 'Example').html('Error uploading ' + createFileHTML(file) + '');
				$('#' + field_id + 'Delete').hide();
			} else {
				$('#' + field_id).val(response);
				$('#' + field_id + 'Example').html('Uploaded ' + createFileHTML(file));
				$('#' + field_id + 'Delete').show();
			}
		}
	});
}

function deleteFile(field_id) {
	$('#' + field_id).val('*deleted*');
	$('#' + field_id + 'Example').html('File deleted');
	$('#' + field_id + 'Delete').hide();
}

function createFileHTML(filename) {
	return '' + filename + '';
}<|endoftext|>


<commit_before>const bodyParser = require("body-parser");
const client = require("./client.js");
const crypto = require("crypto");
const express = require("express");
const NodeRSA = require("node-rsa");
const snekfetch = require("snekfetch");

const app = express();
const port = process.env.PORT || 8080;

app.listen(port, () => {
  console.log("Website is running on http://localhost:" + port);
});

app.get("/", (req, res) => {
  res.redirect("https://github.com/zulip/zulipbot");
});

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

app.post("/", (req, res) => {
  if (req.get("X-GitHub-Event")) {
    const signature = req.get("X-Hub-Signature");
    const hmac = crypto.createHmac("sha1", client.cfg.webhookSecret).update(JSON.stringify(req.body)).digest("hex");
    if (signature === `sha1=${hmac}`) {
      const validEvent = client.events.get(req.get("X-GitHub-Event"));
      if (validEvent) validEvent.run(client, req.body);
      return res.status(200).send("Valid request");
    }
  } else if (req.get("Travis-Repo-Slug")) {
    snekfetch.get("https://api.travis-ci.org/config").then((r) => {
      const publicKey = JSON.parse(r.text).config.notifications.webhook.public_key;
      const key = new NodeRSA(publicKey, {signingScheme: "sha1"});
      const signature = req.get("Signature");
      const valid = key.verify(JSON.parse(req.body.payload), signature, "base64", "base64");
      if (valid) {
        client.events.get("travis").run(client, JSON.parse(req.body.payload));
        return res.status(200).send("Valid request");
      }
    });
  }
  res.status(500).send("Invalid request");
});

process.on("unhandledRejection", (error, promise) => {
  console.error("An unhandled promise rejection was detected at:", promise);
});

if (client.cfg.checkInactivityTimeout) {
  setInterval(() => {
    client.automations.get("checkInactivity").run(client);
  }, client.cfg.checkInactivityTimeout * 3600000);
}
<commit_msg>bot: Improve webhook handling.<commit_after>const bodyParser = require("body-parser");
const client = require("./client.js");
const crypto = require("crypto");
const express = require("express");
const NodeRSA = require("node-rsa");
const snekfetch = require("snekfetch");

const app = express();
const port = process.env.PORT || 8080;

app.listen(port, () => {
  console.log("Website is running on http://localhost:" + port);
});

app.get("/", (req, res) => {
  res.redirect("https://github.com/zulip/zulipbot");
});

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

app.post("/", async(req, res) => {
  if (req.get("X-GitHub-Event")) {
    const signature = req.get("X-Hub-Signature");
    const secret = client.cfg.webhookSecret.toString();
    const hmac = crypto.createHmac("sha1", secret).update(JSON.stringify(req.body)).digest("hex");
    if (signature === `sha1=${hmac}`) {
      const validEvent = client.events.get(req.get("X-GitHub-Event"));
      if (validEvent) validEvent.run(client, req.body);
      return res.status(200).send("Valid request");
    }
  } else if (req.get("Travis-Repo-Slug")) {
    const r = await snekfetch.get("https://api.travis-ci.org/config");
    const publicKey = JSON.parse(r.text).config.notifications.webhook.public_key;
    const key = new NodeRSA(publicKey, {signingScheme: "sha1"});
    const signature = req.get("Signature");
    const valid = key.verify(JSON.parse(req.body.payload), signature, "base64", "base64");
    if (valid) {
      client.events.get("travis").run(client, JSON.parse(req.body.payload));
      return res.status(200).send("Valid request");
    }
  }
  res.status(500).send("Invalid request");
});

process.on("unhandledRejection", (error, promise) => {
  console.error("An unhandled promise rejection was detected at:", promise);
});

if (client.cfg.checkInactivityTimeout) {
  setInterval(() => {
    client.automations.get("checkInactivity").run(client);
  }, client.cfg.checkInactivityTimeout * 3600000);
}
<|endoftext|>


<commit_before>import {vsprintf} from 'format';
import * as _ from 'underscore';

/**
 *  opts = {};
 *  getL = (ListLangsForUser, callback) => {
 *    callback( error, jsonmsgid, codeLang )
 *  };
 *  listL = (callback) => {
 *    callback( error, ArrayLangs );
 *  };
 *  let app = express();
 *  app.use(new lang(getL, listL, opts).local);
 *
 */

export class lang {

	/**
	 * Lang constructor
	 * @param  {Function} getLang           Get words the trasnlate
	 * @param  {Function} listLang          Get all the list langs
	 * @param  {String} options.localte     Name function, for translate
	 * @param  {String} options.lang        Lang default
	 * @param  {String} options.query       Query in the URL
	 * @param  {String} options.session     In the session the lang
	 * @param  {String} options.errorSelect Error when select and dont have eny
	 * @param  {String} options.errorList   Error when select and dont have eny
	 */
	constructor (getLang, listLang,{
		localte = 't', // t('mystring %d', 1)
		lang = 'en',
		query = 'lang', // ?lang=es_EN
		session ='lang', // req.session.lang
		errorSelect = 'No exist language',
		errorList = 'No have a list of language'
	}) {
		this.error = {
			select : errorSelect,
			error : errorList,
		};
		this.opts = {
			lang,
			query,
			session,
			localte
		};

		if(!_.isFunction(getLang)){
			throw new Error('Need a Store! in GetLang');
		}

		this.getLang = getLang;
		this.listLang = listLang;

	}

	/**
	 * setLang Gate all the langs with send
	 * @param {object}   req Request
	 * @param {Function} fn  Callback function
	 */
	__setLang (req, fn) {
		var name = [];

		if( _.isString(req.query[this.opts.query]) ){
			name.push(req.query[this.opts.query].toLowerCase().replace('_', '-') );
			name.push(req.query[this.opts.query].toLowerCase().substring(0,2) );
		}

		if(req.session[this.opts.session]){
			name.push(req.session[ this.opts.session ]);
		}

		let header = req.acceptsLanguages().reverse();

		for (let i = header.length - 1; i >= 0; i--){
			name.push( header[i].toLowerCase().replace('_', '-') );
			name.push( header[i].toLowerCase().substring(0,2) );
		}

		name.push( this.opts.lang );

		this.getLang(name, fn);
	}

	/**
	 * translate System with translate
	 * @param  {Object} lang Lang with translate
	 * @return {Function}    Function with create the string
	 */
	__translate (lang={}) {
		/**
		 * Locatie or T
		 * @param  {String}  arg[0] Tag or msgid
		 * @param  {Any}    arg[..] Well the thing to add into the string
		 * @return {String}         Translate
		 */
		return (...args) => { // tag, number || string, string, string, string
			var msgid = lang[ args[0] ] || args[0];
			delete args[0];
			args = _.compact(args);

			if( _.isArray(msgid) ){
				msgid = _.compact(msgid);
				var z = msgid[0];
				if(msgid.length > 1 && _.isNumber(args[0]) ){
					for (var i = 0; i < msgid.length; i++) {
						if( i <= args[0] )
							z = msgid[i];
					}
				}
				msgid = z;
			}

			return vsprintf(msgid, args );
		};
	}

	/**
	 * local is the function connect with express/connect
	 * @param  {object}   req  Request
	 * @param  {object}   res  Responce
	 * @param  {Function} next Next with next function in your app
	 */
	local (req, res, next) {
		this.__setLang(req, (err, lang, code) => {
			if(err || !code){
				return next(err || new Error(this.error.select));
			}

			res[ this.opts.localte ] = res.locals[ this.opts.localte ] = this.__translate(lang);

			if(req.session && code ){
				req.session[ this.opts.session ] = code;
			}

			if( !this.listLang ){
				return next();
			}

			this.listLang((err, langs) => {
				if(err || !langs){
					return next(err || new Error(this.error.list));
				}

				res[ `${this.opts.localte}List` ] = res.locals[ `${this.opts.localte}List` ] = langs;
				next();
			});
		});
	}
}
<commit_msg>Example<commit_after>import {vsprintf} from 'format';
import * as _ from 'underscore';

/**
 * Example:
 *
 *  import {lang} form 'express-lang';
 *  let opts = { ...Options };
 *  let getL = (ListLangsForUser, callback) => {
 *    callback( error, jsonmsgid, codeLang )
 *  };
 *  let listL = (callback) => {
 *    callback( error, ArrayLangs );
 *  };
 *  let app = express();
 *  app.use(new lang(getL, listL, opts).local);
 *
 */

export class lang {

	/**
	 * Lang constructor
	 * @param  {Function} getLang           Get words the trasnlate
	 * @param  {Function} listLang          Get all the list langs
	 * @param  {String} options.localte     Name function, for translate
	 * @param  {String} options.lang        Lang default
	 * @param  {String} options.query       Query in the URL
	 * @param  {String} options.session     In the session the lang
	 * @param  {String} options.errorSelect Error when select and dont have eny
	 * @param  {String} options.errorList   Error when select and dont have eny
	 */
	constructor (getLang, listLang,{
		localte = 't', // t('mystring %d', 1)
		lang = 'en',
		query = 'lang', // ?lang=es_EN
		session ='lang', // req.session.lang
		errorSelect = 'No exist language',
		errorList = 'No have a list of language'
	}) {
		this.error = {
			select : errorSelect,
			error : errorList,
		};
		this.opts = {
			lang,
			query,
			session,
			localte
		};

		if(!_.isFunction(getLang)){
			throw new Error('Need a Store! in GetLang');
		}

		this.getLang = getLang;
		this.listLang = listLang;

	}

	/**
	 * setLang Gate all the langs with send
	 * @param {object}   req Request
	 * @param {Function} fn  Callback function
	 */
	__setLang (req, fn) {
		var name = [];

		if( _.isString(req.query[this.opts.query]) ){
			name.push(req.query[this.opts.query].toLowerCase().replace('_', '-') );
			name.push(req.query[this.opts.query].toLowerCase().substring(0,2) );
		}

		if(req.session[this.opts.session]){
			name.push(req.session[ this.opts.session ]);
		}

		let header = req.acceptsLanguages().reverse();

		for (let i = header.length - 1; i >= 0; i--){
			name.push( header[i].toLowerCase().replace('_', '-') );
			name.push( header[i].toLowerCase().substring(0,2) );
		}

		name.push( this.opts.lang );

		this.getLang(name, fn);
	}

	/**
	 * translate System with translate
	 * @param  {Object} lang Lang with translate
	 * @return {Function}    Function with create the string
	 */
	__translate (lang={}) {
		/**
		 * Locatie or T
		 * @param  {String}  arg[0] Tag or msgid
		 * @param  {Any}    arg[..] Well the thing to add into the string
		 * @return {String}         Translate
		 */
		return (...args) => { // tag, number || string, string, string, string
			var msgid = lang[ args[0] ] || args[0];
			delete args[0];
			args = _.compact(args);

			if( _.isArray(msgid) ){
				msgid = _.compact(msgid);
				var z = msgid[0];
				if(msgid.length > 1 && _.isNumber(args[0]) ){
					for (var i = 0; i < msgid.length; i++) {
						if( i <= args[0] )
							z = msgid[i];
					}
				}
				msgid = z;
			}

			return vsprintf(msgid, args );
		};
	}

	/**
	 * local is the function connect with express/connect
	 * @param  {object}   req  Request
	 * @param  {object}   res  Responce
	 * @param  {Function} next Next with next function in your app
	 */
	local (req, res, next) {
		this.__setLang(req, (err, lang, code) => {
			if(err || !code){
				return next(err || new Error(this.error.select));
			}

			res[ this.opts.localte ] = res.locals[ this.opts.localte ] = this.__translate(lang);

			if(req.session && code ){
				req.session[ this.opts.session ] = code;
			}

			if( !this.listLang ){
				return next();
			}

			this.listLang((err, langs) => {
				if(err || !langs){
					return next(err || new Error(this.error.list));
				}

				res[ `${this.opts.localte}List` ] = res.locals[ `${this.opts.localte}List` ] = langs;
				next();
			});
		});
	}
}
<|endoftext|>


<commit_before>const HTTPS = true
const API_HOST = 'backoffice.2cnnct.com'
const API_VERSION = 1

const resources = {
  Users: require('./resources/Users'),
  Workplaces: require('./resources/Workplaces')
}

class Deskbookers {
  constructor () {
    this.api = {
      https: HTTPS,
      host: API_HOST,
      version: API_VERSION
    }

    for (let name in resources) {
      this[`${name.toLowerCase()}`] = new resources[name](this.api)
    }
  }
}

module.exports = Deskbookers
<commit_msg>Adds ability to set API host from constructor<commit_after>const HTTPS = true
const API_HOST = 'backoffice.2cnnct.com'
const API_VERSION = 1

const resources = {
  Users: require('./resources/Users'),
  Workplaces: require('./resources/Workplaces')
}

class Deskbookers {
  constructor (apiHost = API_HOST) {
    this.api = {
      https: HTTPS,
      host: apiHost,
      version: API_VERSION
    }

    for (let name in resources) {
      this[`${name.toLowerCase()}`] = new resources[name](this.api)
    }
  }
}

module.exports = Deskbookers
<|endoftext|>


<commit_before>/**
 * # Closure Eliminator
 */

// Do not use Symbol here. class constructor/body missed Symbol-props
const $classConstructor = '__classConstructor';
const $classMethod = '__classMethod';
const $objectMethod = '__objectMethod';
const $bindedArrowFunction = '__bindedArrowFunction';

export default function build(babel:Object):Object {
  const {types: t} = babel;

  return {
    visitor: {
      Function: {
        exit (path) {
          const {node} = path;
          if (node[$classConstructor] || node.body[$classConstructor] || node[$classMethod] || node[$objectMethod] || node[$bindedArrowFunction]) {
            return;
          }
          if (path.findParent(({node}) => node._generated || node._compact)) {
            path.skip();
            return;
          }
          const bestParentScope = getBestHoistedScope(path);
          if (bestParentScope !== path.scope.parent) {
            const attachPath = getAttachmentPosition(bestParentScope.path, path);
            moveToNewPosition(path, attachPath);
          }
        }
      },
      ObjectExpression: {
        enter(path) {
          path.traverse({
            ObjectMethod: {
              enter({node}) {
                node[$objectMethod] = node.body[$objectMethod] = true;
              }
            }
          });
        }
      },
      Class: {
        enter(path) {
          const node = path.node;
          node[$classConstructor] = node.body[$classConstructor] = true;
          path.traverse({
            ClassMethod: {
              enter({node}) {
                node[$classMethod] = node.body[$classMethod] = true;
              }
            }
          });
        }
      },
      ThisExpression: {
        enter(path) {
          var parentArrow = path;
          while (parentArrow = parentArrow.findParent(node=>node.type === 'ArrowFunctionExpression')) {
            parentArrow.node[$bindedArrowFunction] = true;
          }
        }
      }
    }
  };

  function getBestHoistedScope(path) {
    const parentScopes = getAllParentScopes(path.scope),
      parentBindings = path.scope.parent.getAllBindings();
    for (let id in parentBindings) {
      let hasUsageOfBinding = []
        .concat(parentBindings[id].referencePaths)
        .concat(parentBindings[id].constantViolations)
        .some(subPath => subPath.getAncestry().indexOf(path) !== -1);
      if (hasUsageOfBinding) {
        let idx = parentScopes.indexOf(parentBindings[id].scope);
        if (idx !== -1) {
          parentScopes.splice(idx + 1, Infinity);
        }
      }
    }
    return parentScopes.pop();
  }

  function getAllParentScopes(scope) {
    var scopes = [];
    while (scope = scope.parent) {
      scopes.push(scope);
    }
    return scopes;
  }

  function getAttachmentPosition(bestParent, prevPath) {
    let possibleSiblings;
    if (bestParent.isFunction()) {
      possibleSiblings = bestParent.get('body.body');
    } else if (bestParent.isProgram() || bestParent.isBlockStatement()) {
      possibleSiblings = bestParent.get('body');
    }
    const prevParents = prevPath.getAncestry();
    return possibleSiblings.find(x => prevParents.indexOf(x) !== -1);
  }

  function moveToNewPosition(path, attachPath) {
    const {node, scope} = path,
      newScope = attachPath.parentPath.scope;
    if (node.type === 'FunctionDeclaration') {
      let uid = newScope.generateUidIdentifierBasedOnNode(node.id);
      scope.rename(node.id.name, uid.name);
      scope.moveBindingTo(uid.name, newScope);
      node._hoisted = true;
      attachPath.insertBefore([node]);
      path.remove();
    } else {
      const uid = path.parentPath.scope.generateUidIdentifierBasedOnNode(node.id),
        replacement = t.functionDeclaration(
          uid,
          node.params,
          normalizeFunctionBody(node.body)
        );
      replacement.loc = node.loc;
      replacement.generator = node.generator;
      replacement.async = node.async;
      replacement._hoisted = true;
      attachPath.insertBefore([replacement]);
      path.replaceWith(t.identifier(uid.name));
      newScope.crawl();
    }
  }

  /**
   * Normalize a function body so that it is always a BlockStatement.
   */
  function normalizeFunctionBody(node:Object):Object {
    if (node.type !== 'BlockStatement') {
      return t.blockStatement([
        t.returnStatement(node)
      ]);
    }
    else {
      return node;
    }
  }
}
<commit_msg>simplify code<commit_after>/**
 * # Closure Eliminator
 */

// Do not use Symbol here. class constructor/body missed Symbol-props
const $classConstructor = '__classConstructor';
const $classMethod = '__classMethod';
const $objectMethod = '__objectMethod';
const $bindedArrowFunction = '__bindedArrowFunction';

export default function build(babel:Object):Object {
  const {types: t} = babel;

  return {
    visitor: {
      Function: {
        exit (path) {
          const {node} = path;
          if (node[$classConstructor] || node.body[$classConstructor] || node[$classMethod] || node[$objectMethod] || node[$bindedArrowFunction]) {
            return;
          }
          if (path.findParent(({node}) => node._generated || node._compact)) {
            path.skip();
            return;
          }
          const bestParentScope = getBestHoistedScope(path);
          if (bestParentScope !== path.scope.parent) {
            const attachPath = getAttachmentPosition(bestParentScope.path, path);
            moveToNewPosition(path, attachPath);
          }
        }
      },
      ObjectMethod: {
        enter({node}) {
          node[$objectMethod] = node.body[$objectMethod] = true;
        }
      },
      Class: {
        enter({node}) {
          node[$classConstructor] = node.body[$classConstructor] = true;
        }
      },
      ClassMethod: {
        enter({node}) {
          node[$classMethod] = node.body[$classMethod] = true;
        }
      },
      ThisExpression: {
        enter(path) {
          path.getAncestry()
            .filter(path=>path.type === 'ArrowFunctionExpression')
            .forEach(parentArrow => {
              parentArrow.node[$bindedArrowFunction] = true
            });
        }
      }
    }
  };

  function getBestHoistedScope(path) {
    const parentScopes = getAllParentScopes(path.scope),
      parentBindings = path.scope.parent.getAllBindings();
    for (let id in parentBindings) {
      let hasUsageOfBinding = []
        .concat(parentBindings[id].referencePaths)
        .concat(parentBindings[id].constantViolations)
        .some(subPath => subPath.getAncestry().indexOf(path) !== -1);
      if (hasUsageOfBinding) {
        let idx = parentScopes.indexOf(parentBindings[id].scope);
        if (idx !== -1) {
          parentScopes.splice(idx + 1, Infinity);
        }
      }
    }
    return parentScopes.pop();
  }

  function getAllParentScopes(scope) {
    var scopes = [];
    while (scope = scope.parent) {
      scopes.push(scope);
    }
    return scopes;
  }

  function getAttachmentPosition(bestParent, prevPath) {
    let possibleSiblings;
    if (bestParent.isFunction()) {
      possibleSiblings = bestParent.get('body.body');
    } else if (bestParent.isProgram() || bestParent.isBlockStatement()) {
      possibleSiblings = bestParent.get('body');
    }
    const prevParents = prevPath.getAncestry();
    return possibleSiblings.find(x => prevParents.indexOf(x) !== -1);
  }

  function moveToNewPosition(path, attachPath) {
    const {node, scope} = path,
      newScope = attachPath.parentPath.scope;
    if (node.type === 'FunctionDeclaration') {
      let uid = newScope.generateUidIdentifierBasedOnNode(node.id);
      scope.rename(node.id.name, uid.name);
      scope.moveBindingTo(uid.name, newScope);
      node._hoisted = true;
      attachPath.insertBefore([node]);
      path.remove();
    } else {
      const uid = path.parentPath.scope.generateUidIdentifierBasedOnNode(node.id),
        replacement = t.functionDeclaration(
          uid,
          node.params,
          normalizeFunctionBody(node.body)
        );
      replacement.loc = node.loc;
      replacement.generator = node.generator;
      replacement.async = node.async;
      replacement._hoisted = true;
      attachPath.insertBefore([replacement]);
      path.replaceWith(t.identifier(uid.name));
      newScope.crawl();
    }
  }

  /**
   * Normalize a function body so that it is always a BlockStatement.
   */
  function normalizeFunctionBody(node:Object):Object {
    if (node.type !== 'BlockStatement') {
      return t.blockStatement([
        t.returnStatement(node)
      ]);
    }
    else {
      return node;
    }
  }
}
<|endoftext|>


<commit_before>import React from 'react';
import {observer} from 'mobx-react';

import {Router} from 'director';

import StatusHeader from './statusHeader';
import StatusEntry from './statusEntry';
import StatusOverview from './statusOverview';

import { ALL_STATUSES, PUBLIC_STATUSES, FRIEND_STATUSES } from '../constants';

// Needed for onTouchTap
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();

import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import RaisedButton from 'material-ui/RaisedButton';

@observer
export default class StatusApp extends React.Component {
	render() {
		const {statusStore, viewStore} = this.props;

		return (

                <MuiThemeProvider>
                    <RaisedButton label="Default" />
                </MuiThemeProvider>
                <StatusHeader statusStore={statusStore} viewStore={viewStore} />
                <StatusEntry statusStore={statusStore} />
				<StatusOverview statusStore={statusStore} viewStore={viewStore} />

		);
	}

	componentDidMount() {
		var viewStore = this.props.viewStore;

		var router = Router({
			'/': function() { viewStore.statusFilter = ALL_STATUSES; },
			'/public': function() { viewStore.statusFilter = PUBLIC_STATUSES; },
			'/friend': function() { viewStore.statusFilter = FRIEND_STATUSES; }
		});
		router.init('/');
	}
}

StatusApp.propTypes = {
	viewStore: React.PropTypes.object.isRequired,
	statusStore: React.PropTypes.object.isRequired
};
<commit_msg>Use a custom <MuiThemeProvider muiTheme={muiTheme}><commit_after>import React from 'react';
import {observer} from 'mobx-react';

import {Router} from 'director';

import StatusHeader from './statusHeader';
import StatusEntry from './statusEntry';
import StatusOverview from './statusOverview';

import { ALL_STATUSES, PUBLIC_STATUSES, FRIEND_STATUSES } from '../constants';

// Needed for onTouchTap
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();

import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import {green100, green500, green700} from 'material-ui/styles/colors';
import AppBar from 'material-ui/AppBar';

@observer
export default class StatusApp extends React.Component {
	render() {
		const {statusStore, viewStore} = this.props;

        const muiTheme = getMuiTheme({
          palette: {
            primary1Color: green500,
            primary2Color: green700,
            primary3Color: green100,
          },
        }, {
          avatar: {
            borderColor: null,
          }
        });

		return (
            <MuiThemeProvider muiTheme={muiTheme}>

                    <AppBar title="Josoroma" />
                    <StatusHeader statusStore={statusStore} viewStore={viewStore} />
                    <StatusEntry statusStore={statusStore} />
    				<StatusOverview statusStore={statusStore} viewStore={viewStore} />

            </MuiThemeProvider>
		);
	}

	componentDidMount() {
		var viewStore = this.props.viewStore;

		var router = Router({
			'/': function() { viewStore.statusFilter = ALL_STATUSES; },
			'/public': function() { viewStore.statusFilter = PUBLIC_STATUSES; },
			'/friend': function() { viewStore.statusFilter = FRIEND_STATUSES; }
		});
		router.init('/');
	}
}

StatusApp.propTypes = {
	viewStore: React.PropTypes.object.isRequired,
	statusStore: React.PropTypes.object.isRequired
};
<|endoftext|>


<commit_before>import pre from "./pre";
import post from "./post";
export {pre,post};

import fetchLatest from "./steps/fetch-latest";
import verify from "./steps/verify";
import nextVersion from "./steps/next-version";
import prepPublish from "./steps/prep-publish";
import generateChangelog from "./steps/generate-changelog";
import publishChangelog from "./steps/publish-changelog";
export {fetchLatest,verify,nextVersion,prepPublish,generateChangelog,publishChangelog};

import options from "./options";
import pipeline from "./pipeline";
export {options,pipeline};
<commit_msg>fix: index step references<commit_after>import pre from "./pre";
import post from "./post";
export {pre,post};

import fetchLatest from "./steps/fetch-latest";
import verify from "./steps/verify";
import configureNpm from "./steps/configure-npm";
import fetchCommits from "./steps/fetch-commits";
import resolveVersion from "./steps/resolve-version";
import prepPublish from "./steps/prep-publish";
import generateChangelog from "./steps/generate-changelog";
import publishChangelog from "./steps/publish-changelog";
export {fetchLatest,verify,configureNpm,fetchCommits,resolveVersion,prepPublish,generateChangelog,publishChangelog};

import options from "./options";
import pipeline from "./pipeline";
export {options,pipeline};
<|endoftext|>


<commit_before>import React from 'react';
import { connect } from 'react-redux'
import { Field, reduxForm } from 'redux-form'

import { Card, CardActions, CardHeader, CardText } from 'material-ui/Card'
import { SelectField, TextField } from 'redux-form-material-ui'
import MenuItem from 'material-ui/MenuItem'
import FlatButton from 'material-ui/FlatButton'
import FontIcon from 'material-ui/FontIcon'

import { cardSpace } from '../../lib/styles'
import { Row, Col } from 'react-flexbox-grid/lib/index'

import { gotoScreen } from '../../store/screenActions'
import { positive, number, required, address } from '../../lib/validators'
import log from 'loglevel'

const Render = ({fields: {from, to}, account, handleSubmit, resetForm, submitting, cancel}) => {
    log.debug('fields - from', from);

    return (
        <Card style={cardSpace}>
            <CardHeader
                title='Send Transaction'
                actAsExpander={false}
                showExpandableButton={false}
            />

            <CardText expandable={false}>
                <Row>
                    <Col xs={12} md={6}>
                        <Row>
                            <Col xs={12}>
                                <Field name="from"
                                       floatingLabelText="From"
                                       component={SelectField}
                                       fullWidth={true}>
                                    <MenuItem value={account.get('id')} primaryText={account.get('id')} />
                                </Field>
                            </Col>
                        </Row>
                        <Row>
                            <Col xs={12}>
                                <Field name="to"
                                       component={TextField}
                                       floatingLabelText="Target Address"
                                       hintText="0x0000000000000000000000000000000000000000"
                                       fullWidth={true}
                                       validate={[required, address]}
                                />
                            </Col>
                        </Row>
                        <Row>
                            <Col xs={12}>
                                <Field name="value"
                                       component={TextField}
                                       floatingLabelText="Amount (Ether)"
                                       hintText="1.0000"
                                       defaultValue={0.0}
                                       validate={[required, number, positive]}
                                />
                            </Col>
                        </Row>
                    </Col>

                    <Col xs={12} md={6}>
                        <Row>
                            <Col xs={12}>
                                <Field name="gasPrice"
                                       component={TextField}
                                       floatingLabelText="Gas Price (MGas)"
                                       hintText="10000"
                                       validate={[required, number, positive]}
                                />
                            </Col>
                        </Row>
                        <Row>
                            <Col xs={12}>
                                <Field name="gasAmount"
                                       component={TextField}
                                       floatingLabelText="Gas Amount"
                                       hintText="21000"
                                       validate={[required, number, positive]}
                                />
                            </Col>
                        </Row>
                    </Col>
                </Row>

            </CardText>

            <CardActions>
                <FlatButton label="Send"
                            icon={<FontIcon className="fa fa-check" />}/>
                <FlatButton label="Cancel"
                            onClick={cancel}
                            icon={<FontIcon className="fa fa-ban" />}/>
            </CardActions>
        </Card>
    )
};

const CreateTxForm = reduxForm({
    form: 'createTx',
    fields: ['to', 'from', 'value', 'gasPrice', 'gasAmount']
})(Render);

const CreateTx = connect(
    (state, ownProps) => {
        return {
            initialValues: {
                from: ownProps.account.get('id'),
                gasPrice: 10000,
                gasAmount: 21000
            }
        }
    },
    (dispatch, ownProps) => {
        return {
            cancel: () => {
                dispatch(gotoScreen('home'))
            }
        }
    }
)(CreateTxForm);

export default CreateTx<commit_msg>components/tx/create.js: send tx to server<commit_after>import React from 'react';
import { connect } from 'react-redux'
import { Field, reduxForm } from 'redux-form'

import { Card, CardActions, CardHeader, CardText } from 'material-ui/Card'
import { SelectField, TextField } from 'redux-form-material-ui'
import MenuItem from 'material-ui/MenuItem'
import FlatButton from 'material-ui/FlatButton'
import FontIcon from 'material-ui/FontIcon'

import { cardSpace } from '../../lib/styles'
import { Row, Col } from 'react-flexbox-grid/lib/index'

import { sendTransaction } from 'store/accountActions'
import Immutable from 'immutable'
import { gotoScreen } from 'store/screenActions'
import { positive, number, required, address } from '../../lib/validators'
import log from 'loglevel'

const Render = ({fields: {from, to}, account, handleSubmit, invalid, pristine, resetForm, submitting, cancel}) => {
    log.debug('fields - from', from);

    return (
        <Card style={cardSpace}>
            <CardHeader
                title='Send Transaction'
                actAsExpander={false}
                showExpandableButton={false}
            />

            <CardText expandable={false}>
                <Row>
                    <Col xs={12} md={6}>
                        <Row>
                            <Col xs={12}>
                                <Field name="from"
                                       floatingLabelText="From"
                                       component={SelectField}
                                       fullWidth={true}>
                                    <MenuItem value={account.get('id')} primaryText={account.get('id')} />
                                </Field>
                            </Col>
                        </Row>
                        <Row>
                            <Col xs={12}>
                                <Field name="to"
                                       component={TextField}
                                       floatingLabelText="Target Address"
                                       hintText="0x0000000000000000000000000000000000000000"
                                       fullWidth={true}
                                       validate={[required, address]}
                                />
                            </Col>
                        </Row>
                        <Row>
                            <Col xs={12}>
                                <Field name="value"
                                       component={TextField}
                                       floatingLabelText="Amount (Ether)"
                                       hintText="1.0000"
                                       defaultValue={0.0}
                                       validate={[required, number]}
                                />
                            </Col>
                        </Row>
                    </Col>

                    <Col xs={12} md={6}>
                        <Row>
                            <Col xs={12}>
                                <Field name="gasPrice"
                                       component={TextField}
                                       floatingLabelText="Gas Price (MGas)"
                                       hintText="10000"
                                       validate={[required, number, positive]}
                                />
                            </Col>
                        </Row>
                        <Row>
                            <Col xs={12}>
                                <Field name="gasAmount"
                                       component={TextField}
                                       floatingLabelText="Gas Amount"
                                       hintText="21000"
                                       validate={[required, number, positive]}
                                />
                            </Col>
                        </Row>
                    </Col>
                </Row>

            </CardText>

            <CardActions>
                <FlatButton label="Send"
                            disabled={pristine || submitting || invalid }
                            onClick={handleSubmit}
                            icon={<FontIcon className="fa fa-check" />}/>
                <FlatButton label="Cancel"
                            onClick={cancel}
                            icon={<FontIcon className="fa fa-ban" />}/>
            </CardActions>
        </Card>
    )
};

const CreateTxForm = reduxForm({
    form: 'createTx',
    fields: ['to', 'from', 'value', 'gasPrice', 'gasAmount']
})(Render);

const CreateTx = connect(
    (state, ownProps) => {
        return {
            initialValues: {
                from: ownProps.account.get('id'),
                gasPrice: 10000,
                gasAmount: 21000
            },
            accounts: state.accounts.get('accounts', Immutable.List())
        }
    },
    (dispatch, ownProps) => {
        return {
            onSubmit: data => {
                return new Promise((resolve, reject) => {
                    dispatch(sendTransaction(data.from, data.to, data.gasAmount, data.gasPrice, data.value))
                        .then((response) => {
                            dispatch(gotoScreen('transaction', {
                                                                transaction: response,
                                                                account: ownProps.account
                                                                }))
                            resolve(response);
                        });
                    });
            },
            cancel: () => {
                dispatch(gotoScreen('home'))
            }
        }
    }
)(CreateTxForm);

export default CreateTx<|endoftext|>


<commit_before>import {colors} from "./color.js";
import {parseSchema} from "./schema.js";
import {each} from "./util.js";
import {evaluate} from "./evaluate.js";
import {select, selectAll} from "./render/selection.js";
import {createContext} from "./context.js";

//Get or set context values
let getOrSetContextValue = function (self, node, name, value) {
    //Check if node is not defined
    if (typeof node === "undefined" || node === null) {
        throw new Error(`Unknown type "${name}"`); //TODO: improve error
    }
    //Check for no value provided --> return the current node value
    if (typeof value === "undefined") {
        return node.value;
    }
    //Update the data input
    return self.context.addAction(node, value);
};

//Default options
let defaultOptions = {
    "parent": null
};

//Main Jviz instance
let jviz = function (schema, options) {
    //Hack to remove the new operator
    if (!(this instanceof jviz)) {
        return new jviz(schema, options);
    }
    //TODO: validate options object
    //this.options = options;
    options = Object.assing({}, defaultOptions, options);
    //Create the new viewer context
    this.context = createContext(schema, options);
    //Check for parent component provided
    if (typeof options.parent !== "undefined" && options.parent !== null) {
        this.context.scene.mount(parent); //Mount scene
    }
};

//Initialize api methods
jviz.prototype = {
    //Render the plot
    "render": function (callback) {
        return this.context.render(callback);
    },
    //Get or set context state values
    "state": function (name, value) {
        return getOrSetContextValue(this, this.context.state[name], name, value);
    },
    //Get or set the context data
    "data": function (name, value) {
        return getOrSetContextValue(this, this.context.input[name], name, value);
    },
    //Get or set the plot width
    "width": function (value) {
        return getOrSetContextValue(this, this.context.draw.width, "width", value);
    },
    //Get or set the plot height
    "height": function (value) {
        return getOrSetContextValue(this, this.context.draw.height, "height", value);
    },
    //Get or set the plot padding
    "padding": function (value) {
        return getOrSetContextValue(this, this.context.draw.padding, "padding", value);
    },
    //Register event listener
    "addEventListener": function (name, listener) {
        return this.context.events.addEventListener(name, listener);
    },
    //Remove event listener
    "removeEventListener": function (name, listener) {
        return this.context.events.removeEventListener(name, listener);
    },
    //Destroy the viewer --> unmount the scene and remove listeners
    "destroy": function () {
        this.context.scene.unmount(); //Unmount scene
        //TODO: remove context listeners
    }
};

//Assign some utils
Object.assign(jviz, {
    "colors": colors,
    "each": each,
    "evaluate": evaluate,
    "schema": parseSchema,
    "select": select,
    "selectAll": selectAll
});

//Export jviz
export default jviz;

<commit_msg>Fixed minor typo in jviz constructor<commit_after>import {colors} from "./color.js";
import {parseSchema} from "./schema.js";
import {each} from "./util.js";
import {evaluate} from "./evaluate.js";
import {select, selectAll} from "./render/selection.js";
import {createContext} from "./context.js";

//Get or set context values
let getOrSetContextValue = function (self, node, name, value) {
    //Check if node is not defined
    if (typeof node === "undefined" || node === null) {
        throw new Error(`Unknown type "${name}"`); //TODO: improve error
    }
    //Check for no value provided --> return the current node value
    if (typeof value === "undefined") {
        return node.value;
    }
    //Update the data input
    return self.context.addAction(node, value);
};

//Default options
let defaultOptions = {
    "parent": null
};

//Main Jviz instance
let jviz = function (schema, options) {
    //Hack to remove the new operator
    if (!(this instanceof jviz)) {
        return new jviz(schema, options);
    }
    //TODO: validate options object
    //this.options = options;
    options = Object.assign({}, defaultOptions, options);
    //Create the new viewer context
    this.context = createContext(schema, options);
    //Check for parent component provided
    if (typeof options.parent !== "undefined" && options.parent !== null) {
        this.context.scene.mount(parent); //Mount scene
    }
};

//Initialize api methods
jviz.prototype = {
    //Render the plot
    "render": function (callback) {
        return this.context.render(callback);
    },
    //Get or set context state values
    "state": function (name, value) {
        return getOrSetContextValue(this, this.context.state[name], name, value);
    },
    //Get or set the context data
    "data": function (name, value) {
        return getOrSetContextValue(this, this.context.input[name], name, value);
    },
    //Get or set the plot width
    "width": function (value) {
        return getOrSetContextValue(this, this.context.draw.width, "width", value);
    },
    //Get or set the plot height
    "height": function (value) {
        return getOrSetContextValue(this, this.context.draw.height, "height", value);
    },
    //Get or set the plot padding
    "padding": function (value) {
        return getOrSetContextValue(this, this.context.draw.padding, "padding", value);
    },
    //Register event listener
    "addEventListener": function (name, listener) {
        return this.context.events.addEventListener(name, listener);
    },
    //Remove event listener
    "removeEventListener": function (name, listener) {
        return this.context.events.removeEventListener(name, listener);
    },
    //Destroy the viewer --> unmount the scene and remove listeners
    "destroy": function () {
        this.context.scene.unmount(); //Unmount scene
        //TODO: remove context listeners
    }
};

//Assign some utils
Object.assign(jviz, {
    "colors": colors,
    "each": each,
    "evaluate": evaluate,
    "schema": parseSchema,
    "select": select,
    "selectAll": selectAll
});

//Export jviz
export default jviz;

<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import registerServiceWorker from './registerServiceWorker';
import './index.css';

const isOnDev = window.location.host.indexOf('.dev') !== -1
const isOnDomain = window.location.host.indexOf('.design') !== -1
const isOnHttps = window.location.protocol.indexOf('https') !== -1
if (isOnDomain && !isOnHttps)
  window.location.protocol = 'https'
else {
  ReactDOM.render(<App />, document.getElementById('root'));
  if (!isOnDev) registerServiceWorker();
}
<commit_msg>Removing service worker<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import registerServiceWorker from './registerServiceWorker';
import './index.css';

const isOnDev = window.location.host.indexOf('.dev') !== -1
const isOnDomain = window.location.host.indexOf('.design') !== -1
const isOnHttps = window.location.protocol.indexOf('https') !== -1
if (isOnDomain && !isOnHttps)
  window.location.protocol = 'https'
else {
  ReactDOM.render(<App />, document.getElementById('root'));
  // if (!isOnDev) registerServiceWorker();
}
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';

export default class ReactGoogleAutocomplete extends React.Component {
  static propTypes = {
    onPlaceSelected: PropTypes.func,
    types: PropTypes.array,
    componentRestrictions: PropTypes.object,
    bounds: PropTypes.object,
    fields: PropTypes.array,
    inputAutocompleteValue: PropTypes.string,
  };

  constructor(props) {
    super(props);
    this.autocomplete = null;
    this.event = null;
  }

  componentDidMount() {
    const {
      types = ['(cities)'],
      componentRestrictions,
      bounds,
      fields = [
        'address_components',
        'geometry.location',
        'place_id',
        'formatted_address'
      ]
    } = this.props;
    const config = {
      types,
      bounds,
      fields
    };

    if (componentRestrictions) {
      config.componentRestrictions = componentRestrictions;
    }

    this.disableAutofill();

    this.autocomplete = new google.maps.places.Autocomplete(
      this.refs.input,
      config
    );

    this.event = this.autocomplete.addListener(
      'place_changed',
      this.onSelected.bind(this)
    );
  }

  disableAutofill() {
    // Autofill workaround adapted from https://stackoverflow.com/questions/29931712/chrome-autofill-covers-autocomplete-for-google-maps-api-v3/49161445#49161445
    if (window.MutationObserver) {
      const observerHack = new MutationObserver(() => {
        observerHack.disconnect();
        if (this.refs && this.refs.input) {
          this.refs.input.autocomplete = this.props.inputAutocompleteValue || 'new-password';
        }
      });
      observerHack.observe(this.refs.input, {
        attributes: true,
        attributeFilter: ['autocomplete']
      });
    }
  }

  componentWillUnmount() {
    if (this.event) this.event.remove();
  }

  onSelected() {
    if (this.props.onPlaceSelected && this.autocomplete) {
      this.props.onPlaceSelected(this.autocomplete.getPlace(), this.refs.input);
    }
  }

  render() {
    const {
      onPlaceSelected,
      types,
      componentRestrictions,
      bounds,
      ...rest
    } = this.props;

    return <input ref="input" {...rest} />;
  }
}

export class ReactCustomGoogleAutocomplete extends React.Component {
  static propTypes = {
    input: PropTypes.node.isRequired,
    onOpen: PropTypes.func.isRequired,
    onClose: PropTypes.func.isRequired
  };

  constructor(props) {
    super(props);
    this.service = new google.maps.places.AutocompleteService();
  }

  onChange(e) {
    const { types = ['(cities)'] } = this.props;

    if (e.target.value) {
      this.service.getPlacePredictions(
        { input: e.target.value, types },
        (predictions, status) => {
          if (status === 'OK' && predictions && predictions.length > 0) {
            this.props.onOpen(predictions);
          } else {
            this.props.onClose();
          }
        }
      );
    } else {
      this.props.onClose();
    }
  }

  componentDidMount() {
    if (this.props.input.value) {
      this.placeService = new google.maps.places.PlacesService(this.refs.div);
      this.placeService.getDetails(
        { placeId: this.props.input.value },
        (e, status) => {
          if (status === 'OK') {
            this.refs.input.value = e.formatted_address;
          }
        }
      );
    }
  }

  render() {
    return (

        {React.cloneElement(this.props.input, {
          ...this.props,
          ref: 'input',
          onChange: e => {
            this.onChange(e);
          }
        })}

    );
  }
}
<commit_msg>feature to load google maps script if apiKey prop<commit_after>import React from 'react';
import PropTypes from 'prop-types';

export default class ReactGoogleAutocomplete extends React.Component {
  static propTypes = {
    onPlaceSelected: PropTypes.func,
    types: PropTypes.array,
    componentRestrictions: PropTypes.object,
    bounds: PropTypes.object,
    fields: PropTypes.array,
    inputAutocompleteValue: PropTypes.string,
    apiKey: PropTypes.string
  };

  constructor(props) {
    super(props);
    this.autocomplete = null;
    this.event = null;
  }

  componentDidMount() {
    const {
      types = ['(cities)'],
      componentRestrictions,
      bounds,
      apiKey,
      fields = [
        'address_components',
        'geometry.location',
        'place_id',
        'formatted_address'
      ]
    } = this.props;
    const config = {
      types,
      bounds,
      fields
    };

    if (componentRestrictions) {
      config.componentRestrictions = componentRestrictions;
    }

    this.disableAutofill();

    const handleAutoComplete = () => {
      this.autocomplete = new google.maps.places.Autocomplete(
        this.refs.input,
        config
      );

      this.event = this.autocomplete.addListener(
        'place_changed',
        this.onSelected.bind(this)
      );
    };

    if (apiKey) {
      this.handleLoadScript().then(() => handleAutoComplete());
    } else {
      handleAutoComplete();
    }
  }

  disableAutofill() {
    // Autofill workaround adapted from https://stackoverflow.com/questions/29931712/chrome-autofill-covers-autocomplete-for-google-maps-api-v3/49161445#49161445
    if (window.MutationObserver) {
      const observerHack = new MutationObserver(() => {
        observerHack.disconnect();
        if (this.refs && this.refs.input) {
          this.refs.input.autocomplete = this.props.inputAutocompleteValue || 'new-password';
        }
      });
      observerHack.observe(this.refs.input, {
        attributes: true,
        attributeFilter: ['autocomplete']
      });
    }
  }

  componentWillUnmount() {
    if (this.event) this.event.remove();
  }

  onSelected() {
    if (this.props.onPlaceSelected && this.autocomplete) {
      this.props.onPlaceSelected(this.autocomplete.getPlace(), this.refs.input);
    }
  }

  handleLoadScript = () => {
    const googleMapsScriptUrl = `https://maps.googleapis.com/maps/api/js?key=${this.props.apiKey}&libraries=places`;

    // Check if script exists already
    if (
      document.querySelectorAll(`script[src="${googleMapsScriptUrl}"]`).length >
      0
    ) {
      return Promise.resolve();
    }

    this.googleMapsScript = document.createElement('script');
    this.googleMapsScript.src = googleMapsScriptUrl;

    document.body.appendChild(this.googleMapsScript);

    return new Promise((resolve) => {
      this.googleMapsScript.addEventListener('load', () => resolve());
    });
  };

  render() {
    const {
      onPlaceSelected,
      types,
      componentRestrictions,
      bounds,
      apiKey,
      ...rest
    } = this.props;

    return <input ref="input" {...rest} />;
  }
}

export class ReactCustomGoogleAutocomplete extends React.Component {
  static propTypes = {
    input: PropTypes.node.isRequired,
    onOpen: PropTypes.func.isRequired,
    onClose: PropTypes.func.isRequired
  };

  constructor(props) {
    super(props);
    this.service = new google.maps.places.AutocompleteService();
  }

  onChange(e) {
    const { types = ['(cities)'] } = this.props;

    if (e.target.value) {
      this.service.getPlacePredictions(
        { input: e.target.value, types },
        (predictions, status) => {
          if (status === 'OK' && predictions && predictions.length > 0) {
            this.props.onOpen(predictions);
          } else {
            this.props.onClose();
          }
        }
      );
    } else {
      this.props.onClose();
    }
  }

  componentDidMount() {
    if (this.props.input.value) {
      this.placeService = new google.maps.places.PlacesService(this.refs.div);
      this.placeService.getDetails(
        { placeId: this.props.input.value },
        (e, status) => {
          if (status === 'OK') {
            this.refs.input.value = e.formatted_address;
          }
        }
      );
    }
  }

  render() {
    return (

        {React.cloneElement(this.props.input, {
          ...this.props,
          ref: 'input',
          onChange: e => {
            this.onChange(e);
          }
        })}

    );
  }
}
<|endoftext|>


<commit_before>import {SystemJSLoader} from 'aurelia-loader-systemjs';
import {Aurelia, LogManager} from 'aurelia-framework';
import {ConsoleAppender} from 'aurelia-logging-console';

var logger = LogManager.getLogger('bootstrapper');

function ready(global) {
  return new Promise((resolve, reject) =>{
    if (global.document.readyState === "complete" ) {
      resolve(global.document);
    } else {
      global.document.addEventListener("DOMContentLoaded", completed, false);
      global.addEventListener("load", completed, false);
    }

    function completed() {
      global.document.removeEventListener("DOMContentLoaded", completed, false);
      global.removeEventListener("load", completed, false);
      resolve(global.document);
    }
  });
}

function loadPolyfills(){
  return System.normalize('aurelia-bootstrapper').then(function(bootstrapperName){
    return System.normalize('aurelia-framework', bootstrapperName).then(function(frameworkName){
      System.map['aurelia-framework'] = frameworkName;

      return System.normalize('aurelia-loader', frameworkName).then(function(loaderName){
        var toLoad = [];

        if(!System.polyfilled){
          logger.debug('loading core-js');
          toLoad.push(System.normalize('core-js', loaderName).then(function(name){
            return System.import(name);
          }));
        }

        toLoad.push(System.normalize('aurelia-depedency-injection', frameworkName).then(function(name){
          System.map['aurelia-depedency-injection'] = name;
        }));

        toLoad.push(System.normalize('aurelia-router', bootstrapperName).then(function(name){
          System.map['aurelia-router'] = name;
        }));

        toLoad.push(System.normalize('aurelia-logging-console', bootstrapperName).then(function(name){
          System.map['aurelia-logging-console'] = name;
        }));

        if(!('import' in document.createElement('link'))){
          logger.debug('loading the HTMLImports polyfill');
          toLoad.push(System.normalize('webcomponentsjs/HTMLImports.min', loaderName).then(function(name){
            return System.import(name);
          }));
        }

        if(!("content" in document.createElement("template"))){
          logger.debug('loading the HTMLTemplateElement polyfill');
          toLoad.push(System.normalize('aurelia-html-template-element', loaderName).then(function(name){
            return System.import(name);
          }));
        }

        return Promise.all(toLoad);
      });
    });
  });
}

function configureAurelia(aurelia){
  return System.normalize('aurelia-bootstrapper').then(function(bName){
    var toLoad = [];

    toLoad.push(System.normalize('aurelia-templating-binding', bName).then(templatingBinding => {
      aurelia.plugins.installBindingLanguage = function(){
        aurelia.plugins.install(templatingBinding);
        return this;
      };
    }));

    toLoad.push(System.normalize('aurelia-history-browser', bName).then(historyBrowser => {
      return System.normalize('aurelia-templating-router', bName).then(templatingRouter => {
        aurelia.plugins.installRouter = function(){
          aurelia.plugins.install(historyBrowser);
          aurelia.plugins.install(templatingRouter);
          return this;
        };
      });
    }));

    toLoad.push(System.normalize('aurelia-templating-resources', bName).then(name => {
      aurelia.plugins.installResources = function(){
        aurelia.plugins.install(name);
        return this;
      }
    }));

    toLoad.push(System.normalize('aurelia-event-aggregator', bName).then(eventAggregator => {
      System.map['aurelia-event-aggregator'] = eventAggregator;
      aurelia.plugins.installEventAggregator = function(){
        aurelia.plugins.install(eventAggregator);
        return this;
      };
    }));

    return Promise.all(toLoad);
  });
}

function handleMain(mainHost){
  var mainModuleId = mainHost.getAttribute('aurelia-main') || 'main',
      loader = new SystemJSLoader();

  return loader.loadModule(mainModuleId)
    .then(m => {
      var aurelia = new Aurelia(loader);
      return configureAurelia(aurelia).then(() => { return m.configure(aurelia); });
    }).catch(e => {
      setTimeout(function(){ throw e; }, 0);
    });
}

function handleApp(appHost){
  var appModuleId = appHost.getAttribute('aurelia-app') || 'app',
      aurelia = new Aurelia();

  return configureAurelia(aurelia).then(() => {
    aurelia.plugins
      .installBindingLanguage()
      .installResources()
      .installRouter()
      .installEventAggregator();

    return aurelia.start().then(a => { return a.setRoot(appModuleId, appHost); });
  }).catch(e => {
    setTimeout(function(){ throw e; }, 0);
  });
}

function runningLocally(){
  return window.location.protocol !== 'http' &&
    window.location.protocol !== 'https';
}

function run() {
  return ready(window).then(function (doc) {
    var mainHost = doc.querySelectorAll("[aurelia-main]"),
        appHost = doc.querySelectorAll("[aurelia-app]"),
        i, ii;

    if (appHost.length && !mainHost.length && runningLocally()) {
      LogManager.addAppender(new ConsoleAppender());
      LogManager.setLevel(LogManager.levels.debug);
    }

    return loadPolyfills().then(function(){
      for (i = 0, ii = mainHost.length; i < ii; ++i) {
        handleMain(mainHost[i]);
      }

      for (i = 0, ii = appHost.length; i < ii; ++i) {
        handleApp(appHost[i]);
      }
    });
  });
}

run();<commit_msg>feat(all): update to new plugin api<commit_after>import {SystemJSLoader} from 'aurelia-loader-systemjs';
import {Aurelia, LogManager} from 'aurelia-framework';
import {ConsoleAppender} from 'aurelia-logging-console';

var logger = LogManager.getLogger('bootstrapper');

function ready(global) {
  return new Promise((resolve, reject) =>{
    if (global.document.readyState === "complete" ) {
      resolve(global.document);
    } else {
      global.document.addEventListener("DOMContentLoaded", completed, false);
      global.addEventListener("load", completed, false);
    }

    function completed() {
      global.document.removeEventListener("DOMContentLoaded", completed, false);
      global.removeEventListener("load", completed, false);
      resolve(global.document);
    }
  });
}

function loadPolyfills(){
  return System.normalize('aurelia-bootstrapper').then(function(bootstrapperName){
    return System.normalize('aurelia-framework', bootstrapperName).then(function(frameworkName){
      System.map['aurelia-framework'] = frameworkName;

      return System.normalize('aurelia-loader', frameworkName).then(function(loaderName){
        var toLoad = [];

        if(!System.polyfilled){
          logger.debug('loading core-js');
          toLoad.push(System.normalize('core-js', loaderName).then(function(name){
            return System.import(name);
          }));
        }

        toLoad.push(System.normalize('aurelia-depedency-injection', frameworkName).then(function(name){
          System.map['aurelia-depedency-injection'] = name;
        }));

        toLoad.push(System.normalize('aurelia-router', bootstrapperName).then(function(name){
          System.map['aurelia-router'] = name;
        }));

        toLoad.push(System.normalize('aurelia-logging-console', bootstrapperName).then(function(name){
          System.map['aurelia-logging-console'] = name;
        }));

        if(!('import' in document.createElement('link'))){
          logger.debug('loading the HTMLImports polyfill');
          toLoad.push(System.normalize('webcomponentsjs/HTMLImports.min', loaderName).then(function(name){
            return System.import(name);
          }));
        }

        if(!("content" in document.createElement("template"))){
          logger.debug('loading the HTMLTemplateElement polyfill');
          toLoad.push(System.normalize('aurelia-html-template-element', loaderName).then(function(name){
            return System.import(name);
          }));
        }

        return Promise.all(toLoad);
      });
    });
  });
}

function configureAurelia(aurelia){
  return System.normalize('aurelia-bootstrapper').then(function(bName){
    var toLoad = [];

    toLoad.push(System.normalize('aurelia-templating-binding', bName).then(templatingBinding => {
      aurelia.use.defaultBindingLanguage = function(){
        aurelia.use.plugin(templatingBinding);
        return this;
      };
    }));

    toLoad.push(System.normalize('aurelia-history-browser', bName).then(historyBrowser => {
      return System.normalize('aurelia-templating-router', bName).then(templatingRouter => {
        aurelia.use.router = function(){
          aurelia.use.plugin(historyBrowser);
          aurelia.use.plugin(templatingRouter);
          return this;
        };
      });
    }));

    toLoad.push(System.normalize('aurelia-templating-resources', bName).then(name => {
      aurelia.use.defaultResources = function(){
        aurelia.use.plugin(name);
        return this;
      }
    }));

    toLoad.push(System.normalize('aurelia-event-aggregator', bName).then(eventAggregator => {
      System.map['aurelia-event-aggregator'] = eventAggregator;
      aurelia.use.eventAggregator = function(){
        aurelia.use.plugin(eventAggregator);
        return this;
      };
    }));

    return Promise.all(toLoad);
  });
}

function handleMain(mainHost){
  var mainModuleId = mainHost.getAttribute('aurelia-main') || 'main',
      loader = new SystemJSLoader();

  return loader.loadModule(mainModuleId)
    .then(m => {
      var aurelia = new Aurelia(loader);
      return configureAurelia(aurelia).then(() => { return m.configure(aurelia); });
    }).catch(e => {
      setTimeout(function(){ throw e; }, 0);
    });
}

function handleApp(appHost){
  var appModuleId = appHost.getAttribute('aurelia-app') || 'app',
      aurelia = new Aurelia();

  return configureAurelia(aurelia).then(() => {
    aurelia.use
      .defaultBindingLanguage()
      .defaultResources()
      .router()
      .eventAggregator();

    if(appHost.hasAttribute('es5')){
      aurelia.use.es5();
    } else if(appHost.hasAttribute('atscript')){
      aurelia.use.atscript();
    }

    return aurelia.start().then(a => { return a.setRoot(appModuleId, appHost); });
  }).catch(e => {
    setTimeout(function(){ throw e; }, 0);
  });
}

function runningLocally(){
  return window.location.protocol !== 'http' &&
    window.location.protocol !== 'https';
}

function run() {
  return ready(window).then(function (doc) {
    var mainHost = doc.querySelectorAll("[aurelia-main]"),
        appHost = doc.querySelectorAll("[aurelia-app]"),
        i, ii;

    if (appHost.length && !mainHost.length && runningLocally()) {
      LogManager.addAppender(new ConsoleAppender());
      LogManager.setLevel(LogManager.levels.debug);
    }

    return loadPolyfills().then(function(){
      for (i = 0, ii = mainHost.length; i < ii; ++i) {
        handleMain(mainHost[i]);
      }

      for (i = 0, ii = appHost.length; i < ii; ++i) {
        handleApp(appHost[i]);
      }
    });
  });
}

run();<|endoftext|>


<commit_before>'use strict';
var cache = {};

var Emitter = {
    registerHandler(handlerName, handler, spec) {
        if (typeof handler !== 'function') {
            throw new Error(`Handler for the interest ${handlerName} is missing!`);
        }
        cache[handlerName] = cache[handlerName] || [];
        cache[handlerName].push({
            handler: handler,
            spec: spec
        });
    },
    dispatch(handlerName, data) {
        if (cache[handlerName] !== undefined) {
            window.setTimeout(()=> {
                cache[handlerName].forEach(callbackObj => callbackObj.handler.call(callbackObj.spec, data));
            }, 10);
        }
    }
};

function addInterests(spec) {
    var listeners = typeof spec.getListeners === 'function' ? spec.getListeners() : {};

    for (var handler in listeners) {
        if (listeners.hasOwnProperty(handler)) {
            Emitter.registerHandler(handler, spec[listeners[handler]], spec);
        }
    }
}

module.exports = {
    initialize() {
        addInterests(this);
    },
    emit(eventName, eventData) {
        Emitter.dispatch(eventName, eventData);
    }
};
<commit_msg>using new lifecycle method, not calling own event handlers<commit_after>'use strict';
var cache = {};

var Emitter = {
  registerHandler(handlerName, handler, component) {
    if (typeof handler !== 'function') {
      throw new Error(`<${component.name} /> — Handler for the interest ${handlerName} is missing!`);
    }
    cache[handlerName] = cache[handlerName] || [];
    cache[handlerName].push({
      handler: handler,
      component: component
    });
  },
  dispatch(handlerName, data, component) {
    if (cache[handlerName] !== undefined) {
      window.setTimeout(()=> {
        cache[handlerName].forEach(callbackObj => {
          if (callbackObj.component !== component) {
            callbackObj.handler.call(callbackObj.component, data);
          }
        });
      }, 10);
    }
  }
};

function addInterests(component) {
  var listeners = typeof component.getListeners === 'function' ? component.getListeners() : {};

  for (var handler in listeners) {
    if (listeners.hasOwnProperty(handler)) {
      Emitter.registerHandler(handler, component[listeners[handler]], component);
    }
  }
}

module.exports = {
  created() {
    addInterests(this);
  },
  emit(eventName, eventData) {
    Emitter.dispatch(eventName, eventData, this);
  }
};
<|endoftext|>