<commit_before>/**
 * Copyright (c) 2017, salesforce.com, inc.
 * All rights reserved.
 * Licensed under the BSD 3-Clause license.
 * For full license text, see LICENSE.txt file in the repo root or
 * https://opensource.org/licenses/BSD-3-Clause
 */

/**
 * view/rooms/app.js
 *
 * When page is loaded we take all the bots queried and processed
 * to have their UI appended to the page.
 */
const ZERO = 0;
const ONE = 1;
const TWO = 2;
const THREE = 3;
const botsContainer = document.getElementById('botsContainer');
const botsLeft = document.getElementById('botsLeftColumn');
const botsMiddle = document.getElementById('botsMiddleColumn');
const botsRight = document.getElementById('botsRightColumn');
const botsContainerColumns = [botsLeft, botsMiddle, botsRight];
const backButton = document.getElementById('backButton');
const activeToggle = document.getElementById('activeToggle');
const confirmButton = document.getElementById('confirm_button');
const declineButton = document.getElementById('decline_button');
const confirmationModal =
  document.getElementById('active_confirmation_modal');
const confirmationText =
  document.getElementById('active_confirmation_text');
const AdmZip = require('adm-zip');
const u = require('../utils');
const uPage = require('./utils/page');
const ROOM_ID = window.location.pathname.split('/rooms/')[ONE];
const urlParameters = window.location.href.includes('?') ?
  window.location.href.split('?')[ONE] : '';
const GET_BOTS = '/v1/bots';
let GET_ROOM = '/v1/rooms/';
GET_ROOM += isNaN(ROOM_ID) ? `?name=${ROOM_ID}` : ROOM_ID;
const GET_EVENTS = '/v1/events';
const GET_ACTIONS = '/v1/botActions';
const GET_ROOMTYPES = '/v1/roomTypes';
const GITHUB_LOGO = '../static/images/GitHub-Mark.png';
const BOT_LOGO = '../static/images/refocus-bot.png';
const BOT_REQ_HEADERS = {
  'X-Requested-With': 'XMLHttpRequest',
  Expires: '-1',
  'Cache-Control': 'private, max-age=31536000', // 31536000s = 1 year
};
let _io;
let _user;
let _roomName;
let _isActive;
let _movingContent;
let _botsLayout;

// Used when holding a bot over a place it can be dropped
const placeholderBot = document.createElement('div');

// Used to drop a bot at the bottom of a column
const blankBot = document.createElement('div');
const botInfo = {};
const DEBUG_REALTIME = window.location.href.split(/[&\?]/)
  .includes('debug=REALTIME');

/**
 * Creates headers for each bot added to the UI
 *
 * @param {Object} msg - Bot response with UI
 * @param {Object} obj - Bot response with UI
 */
function debugMessage(msg, obj) {
  if ((DEBUG_REALTIME) && obj) {
    console.log(msg, obj);
  } else if (DEBUG_REALTIME) {
    console.log(msg);
  }
}

// Listen to message from child window
const eventMethod = window.addEventListener ?
  'addEventListener' : 'attachEvent';
const eventer = window[eventMethod];
const messageEvent = eventMethod === 'attachEvent' ? 'onmessage' : 'message';

eventer(messageEvent, (iframeMessage) => {
  const iframe = document
    .getElementById(iframeMessage.data.name + '-iframe-section');
  iframe.height = iframeMessage.data.height + 'px';
}, false);

/**
 * Called when bot is clicked and dragged
 *
 * @param {Object} event - Dragging bot event.
 */
function botDragHandler(event) {
  // Do not allow a bot to be moved if they are only in 1 column
  if (botsLeft.offsetWidth !== botsContainer.offsetWidth &&
    _movingContent.length) {
    botsContainerColumns.forEach((c) => {
      c.className = 'slds-col slds-large-size--1-of-3' +
        ' slds-size--1-of-1 col-dragging';
    });

    // Setting data to be transferred
    event.dataTransfer.setData('text', event.target.id);
  } else {
    event.preventDefault();
  }
}

/**
 * Called when bot is able to be dropped
 *
 * @param {Object} event - Dragging bot event.
 * @param {DOM} bot - Bot container.
 */
function allowBotDropHandler(event, bot) {
  event.preventDefault();
  const col = bot.parentElement;
  col.insertBefore(placeholderBot, bot);
}

// Resets all columns back to their initial state
function resetColumns() {
  botsContainerColumns.forEach((c) => {
    c.className = 'slds-col slds-large-size--1-of-3 slds-size--1-of-1';
  });

  if (placeholderBot.parentElement) {
    placeholderBot.parentElement.removeChild(placeholderBot);
  }

  if (blankBot.parentElement) {
    blankBot.parentElement.removeChild(blankBot);
  }

  // Resetting variable as no bots are being moved
  _movingContent = null;
}

/**
 * Called when bot is dropped in a valid column.
 *
 * @param {Object} event - Dropping bot event.
 * @param {DOM} col - Column that bot is being dragged over.
 */
function drop(event, col) {
  event.preventDefault();
  const data = event.dataTransfer.getData('text');
  col.insertBefore(document.getElementById(data), placeholderBot);
  const botIframe = document.getElementById(data)
    .getElementsByTagName('iframe')[ZERO];
  const botIframedoc = botIframe.contentDocument;

  if (_movingContent) {
    uPage.writeInIframedoc(botIframedoc, _movingContent);
  }

  resetColumns();
}

/**
 * Decides which column to place the bot in.
 *
 * @param {Int} botName - Name of the bot
 * @param {Int} botIndex - Index of the bot
 * @returns {String} - L, M or R
 */
function decideBotPosition(botName, botIndex) {
  // A bot layout was defined in room/roomType settings
  if (_botsLayout) {
    if (_botsLayout.leftColumn && _botsLayout.leftColumn.includes(botName)) {
      return 'L';
    } else if (_botsLayout.middleColumn &&
      _botsLayout.middleColumn.includes(botName)) {
      return 'M';
    } else if (_botsLayout.rightColumn &&
      _botsLayout.rightColumn.includes(botName)) {
      return 'R';
    }

    return '';
  }

  // No bot layout was defined in settings
  if ((botIndex + ONE) % THREE === ONE) {
    return 'L';
  } else if ((botIndex + ONE) % THREE === TWO) {
    return 'M';
  }

  return 'R';
}

/**
 * Sets up the bots to be movable between columns.
 *
 * @param {DOM} botContainer - Container of bot
 * @param {Int} botName - Name of the bot
 * @param {Int} botIndex - Index of the bot
 */
function setupMovableBots(botContainer, botName, botIndex) {
  botContainer.setAttribute(
    'draggable',
    'true'
  );

  botContainer.addEventListener('dragstart', (e) => {
    botDragHandler(e);
  });

  botContainer.addEventListener('dragend', () => {
    resetColumns();
  });

  botContainer.addEventListener('dragover', (e) => {
    allowBotDropHandler(e, botContainer);
  });

  // Only need to move the bot if the header is clicked
  botContainer.addEventListener('mousedown', (e) => {
    // The bot header was clicked on
    if (e.target.id === 'title-header') {
      const iframe = botContainer.getElementsByTagName('iframe')[ZERO];
      if (iframe.contentDocument) {
        _movingContent = iframe.contentDocument.head.innerHTML +
          iframe.contentDocument.body.innerHTML;
      } else if (iframe.contentWindow) {
        _movingContent = iframe.contentWindow.document.head.innerHTML +
          iframe.contentWindow.document.body.innerHTML;
      }
    }
  });

  const position = decideBotPosition(botName, botIndex);

  if (position === 'L') {
    botsLeft.appendChild(botContainer);
  } else if (position === 'M') {
    botsMiddle.appendChild(botContainer);
  } else if (position === 'R') {
    botsRight.appendChild(botContainer);
  }
}

/**
 * Creates the minimize button for each bot
 *
 * @param {Object} bot - Bot response with UI
 * @returns {DOM} section - Minimize Button
 */
function createMinimizeButton(bot) {
  const minimize = document.createElement('div');
  minimize.className = 'slds-float_right';
  minimize.style.cursor = 'pointer';
  minimize.onclick = () => {
    const botFrame = document.getElementById(bot.name + '-iframe-section');
    const up = document.getElementById(bot.name + '-toggleUp');
    const down = document.getElementById(bot.name + '-toggleDown');
    if (botFrame.style.display === 'none') {
      up.style.display = 'block';
      down.style.display = 'none';
      botFrame.style.display = 'block';
    } else {
      up.style.display = 'none';
      down.style.display = 'block';
      botFrame.style.display = 'none';
    }
  };

  const svgURL = 'http://www.w3.org/2000/svg';
  const xlinkURL = 'http://www.w3.org/1999/xlink';
  const svgElem = document.createElementNS(svgURL, 'svg');
  svgElem.setAttribute('class',
    'slds-icon slds-icon_xx-small slds-m-top_x-small');
  svgElem.setAttribute('style', 'fill:black');
  const useElemUp = document.createElementNS(svgURL, 'use');
  useElemUp.setAttributeNS(
    xlinkURL,
    'xlink:href',
    '../static/icons/utility-sprite/svg/symbols.svg#chevronup'
  );
  useElemUp.setAttribute('id', bot.name + '-toggleUp');
  svgElem.appendChild(useElemUp);
  const useElemDown = document.createElementNS(svgURL, 'use');
  useElemDown.setAttributeNS(
    xlinkURL,
    'xlink:href',
    '../static/icons/utility-sprite/svg/symbols.svg#chevrondown'
  );
  useElemDown.setAttribute('id', bot.name + '-toggleDown');
  useElemDown.setAttribute('style', 'display: none');
  svgElem.appendChild(useElemDown);
  minimize.appendChild(svgElem);

  return minimize;
}

/**
 * Creates headers for each bot added to the UI
 *
 * @param {Object} bot - Bot response with UI
 * @returns {DOM} section - Header section
 */
function createHeader(bot) {
  const section = document.createElement('div');
  section.className = 'slds-section slds-is-open';

  section.setAttribute(
    'style',
    'box-shadow:0px 0px 20px 2px #e2e2e2;margin:1rem;'
  );

  const title = document.createElement('div');
  title.className =
    'slds-p-horizontal_small ' +
    'slds-theme_shade ';

  const text = document.createElement('h3');
  text.id = 'title-header';
  text.className =
    'slds-section__title ';
  text.innerHTML = bot.name;
  text.style.cursor = 'move';

  const circle = document.createElement('div');
  if (bot.active) {
    circle.setAttribute(
      'style',
      'background:#04844b;width:8px;height:8px;border-radius:50%;margin:5px;'
    );
  } else {
    circle.setAttribute(
      'style',
      'background:#c23934;width:8px;height:8px;border-radius:50%;margin:5px;'
    );
  }
  circle.className = 'slds-float_right';

  title.appendChild(createMinimizeButton(bot));
  title.appendChild(text);
  text.appendChild(circle);
  section.appendChild(title);

  return section;
}

/**
 * Creates footers for each bot added to the UI
 *
 * @param {Object} bot - Bot response with UI
 * @returns {DOM} footer - Footer section
 */
function createFooter(bot) {
  const footer = document.createElement('h3');
  const linkedElement = document.createElement('a');
  const gitHubImage = document.createElement('img');
  const botVersion = document.createElement('span');

  footer.className =
    'slds-section__title ' +
    'slds-p-horizontal_small ' +
    'slds-theme_shade ';

  botVersion.innerHTML = 'Version ' + bot.version;
  botVersion.className = 'slds-p-horizontal--medium';
  linkedElement.href = bot.url;
  linkedElement.target = '_blank';
  linkedElement.rel = 'noopener noreferrer';
  gitHubImage.height = '20';
  gitHubImage.width = '20';
  gitHubImage.src = GITHUB_LOGO;
  linkedElement.appendChild(gitHubImage);
  footer.appendChild(linkedElement);
  footer.appendChild(botVersion);

  return footer;
}

/**
 * Dispatches javascript events to bots in iFrames
 *
 * @param {HTMLObject} iframe - iFrame DOM to add Bot UI
 * @param {Object} bot - Bot to add to frame
 * @param {Object} parsedBot - Object that has the HTML and JS as
 *  HTMLObject stored as key value pair
 * @param {Object} currentUser - The current user
 */
function iframeBot(iframe, bot, parsedBot, currentUser) {
  const botScript = parsedBot.js ? parsedBot.js.innerHTML : '';
  const contentSection = parsedBot.html ? parsedBot.html.innerHTML : '';
  let iframedoc = iframe.document;
  if (iframe.contentDocument) {
    iframedoc = iframe.contentDocument;
  } else if (iframe.contentWindow) {
    iframedoc = iframe.contentWindow.document;
  }

  const iframeCss =
    `<link
      rel="stylesheet"
      type="text/css"
      href="/static/css/salesforce-lightning-design-system.2.4.3.min.css">`;

  const iframeJS =
  `
  <script>
    function outputSize(e, browser) {
      if (browser === 'chrome') {
        parent.postMessage(
          {
            "name": "${bot.name}",
            "height": e[0].target.scrollHeight
          }, "*"
        );
      } else {
        parent.postMessage(
        {
          "name": "${bot.name}",
          "height": document.getElementById("${bot.name}").clientHeight < 100 ?
            600 :
            document.getElementById("${bot.name}").clientHeight
        }, "*"
      );
      }
    }

    if( navigator.userAgent.toLowerCase().indexOf('chrome') > -1 ){
      new ResizeObserver(
        function(event) {
          outputSize(event, 'chrome')
        }
      ).observe(document.getElementById("${bot.name}"));
    } else {
      var targetNode = document.getElementById("${bot.name}");
      var config = { attributes: true, childList: true };
      var observer = new MutationObserver(function(mutationsList) {
        for(var mutation of mutationsList) {
          parent.postMessage(
          {
            "name": "${bot.name}",
            "height": document.getElementById("${bot.name}").clientHeight < 100 ?
              600 :
              document.getElementById("${bot.name}").clientHeight
          }, "*")
        }
      });
      observer.observe(targetNode, config);
      window.addEventListener("resize",
        function(event) {
          outputSize(event, 'other');
        }
      );
    }
  </script>`;

  const iframeContent = iframeCss +
      `<script>var user = "${currentUser}"</script>
      ${contentSection}
      <script>${botScript}</script>` +
      iframeJS;

  uPage.writeInIframedoc(iframedoc, iframeContent);
}

/**
 * Create DOM elements for each of the files in the bots zip.
 *
 * @param {Object} bot - Bot response with UI
 * @param {Int} botIndex - Index of Bot
 * @returns {Object} - An object that stores the HTML dom and
 *   javascript dom as key value pairs
 */
function parseBot(bot) {
  const botScript = document.createElement('script');
  botScript.id = bot.name + '-script';

  const contentSection = document.createElement('div');
  contentSection.className = 'slds-section__content';

  try {
    // Unzip bots
    const zip = new AdmZip(new Buffer(bot.ui.data));
    const zipEntries = zip.getEntries();

    // 'index.html' contains root elements that scripts hook up to
    // and needs to be loaded into the DOM first
    const index = zipEntries.filter((entry) => entry.name === 'index.html');
    if (index.length > ZERO) {
      contentSection.innerHTML = zip.readAsText(index[ZERO]);
      const elem = contentSection
        .querySelector('script[src="/index_bundle.js"]');
      if (elem) {
        elem.parentNode.removeChild(elem);
      }
    }

    // go through zipEntries that arent 'index.html'
    zipEntries.filter(
      (entry) => entry.name !== 'index.html'
    ).forEach((script) => {
      botScript.appendChild(
        document.createTextNode(zip.readAsText(script))
      );
    });

    const output = {
      html: contentSection,
      js: botScript,
    };
    return output;
  } catch (exception) {
    debugMessage('Parse Bot failed', exception);
    return {};
  }
} // parseBots

/**
 * Create and fills DOM containers for bots and adds them to
 * to the page.
 *
 * @param {Object} bot - Bot response with UI
 * @param {Int} botIndex - Index of Bot
 */
function displayBot(bot, botIndex) {
  // Get the bots section of the page
  const botContainer = document.createElement('div');
  botContainer.id = bot.name + '-section';
  const headerSection = createHeader(bot);
  const footerSection = createFooter(bot);

  const iframe = document.createElement('iframe');
  iframe.id = bot.name + '-iframe-section';
  iframe.style.display = 'block';
  iframe.style.border = 'none';
  iframe.style.width = '100%';
  iframe.frameBorder = 0;

  headerSection.appendChild(iframe);
  headerSection.appendChild(footerSection);
  botContainer.appendChild(headerSection);
  setupMovableBots(botContainer, bot.name, botIndex);
  const parsedBot = parseBot(bot);

  // user is defined in ./index.pug
  iframeBot(iframe, bot, parsedBot, user);
}<commit_msg>Fix for wobbly iframe bug (#866)<commit_after>/**
 * Copyright (c) 2017, salesforce.com, inc.
 * All rights reserved.
 * Licensed under the BSD 3-Clause license.
 * For full license text, see LICENSE.txt file in the repo root or
 * https://opensource.org/licenses/BSD-3-Clause
 */

/**
 * view/rooms/app.js
 *
 * When page is loaded we take all the bots queried and processed
 * to have their UI appended to the page.
 */
const ZERO = 0;
const ONE = 1;
const TWO = 2;
const THREE = 3;
const MIN_BOT_HEIGHT = 100;
const botsContainer = document.getElementById('botsContainer');
const botsLeft = document.getElementById('botsLeftColumn');
const botsMiddle = document.getElementById('botsMiddleColumn');
const botsRight = document.getElementById('botsRightColumn');
const botsContainerColumns = [botsLeft, botsMiddle, botsRight];
const backButton = document.getElementById('backButton');
const activeToggle = document.getElementById('activeToggle');
const confirmButton = document.getElementById('confirm_button');
const declineButton = document.getElementById('decline_button');
const confirmationModal =
  document.getElementById('active_confirmation_modal');
const confirmationText =
  document.getElementById('active_confirmation_text');
const AdmZip = require('adm-zip');
const u = require('../utils');
const uPage = require('./utils/page');
const ROOM_ID = window.location.pathname.split('/rooms/')[ONE];
const urlParameters = window.location.href.includes('?') ?
  window.location.href.split('?')[ONE] : '';
const GET_BOTS = '/v1/bots';
let GET_ROOM = '/v1/rooms/';
GET_ROOM += isNaN(ROOM_ID) ? `?name=${ROOM_ID}` : ROOM_ID;
const GET_EVENTS = '/v1/events';
const GET_ACTIONS = '/v1/botActions';
const GET_ROOMTYPES = '/v1/roomTypes';
const GITHUB_LOGO = '../static/images/GitHub-Mark.png';
const BOT_LOGO = '../static/images/refocus-bot.png';
const BOT_REQ_HEADERS = {
  'X-Requested-With': 'XMLHttpRequest',
  Expires: '-1',
  'Cache-Control': 'private, max-age=31536000', // 31536000s = 1 year
};
let _io;
let _user;
let _roomName;
let _isActive;
let _movingContent;
let _botsLayout;

// Used when holding a bot over a place it can be dropped
const placeholderBot = document.createElement('div');

// Used to drop a bot at the bottom of a column
const blankBot = document.createElement('div');
const botInfo = {};
const DEBUG_REALTIME = window.location.href.split(/[&\?]/)
  .includes('debug=REALTIME');

/**
 * Creates headers for each bot added to the UI
 *
 * @param {Object} msg - Bot response with UI
 * @param {Object} obj - Bot response with UI
 */
function debugMessage(msg, obj) {
  if ((DEBUG_REALTIME) && obj) {
    console.log(msg, obj);
  } else if (DEBUG_REALTIME) {
    console.log(msg);
  }
}

// Listen to message from child window
const eventMethod = window.addEventListener ?
  'addEventListener' : 'attachEvent';
const eventer = window[eventMethod];
const messageEvent = eventMethod === 'attachEvent' ? 'onmessage' : 'message';

eventer(messageEvent, (iframeMessage) => {
  const iframe = document
    .getElementById(iframeMessage.data.name + '-iframe-section');
  iframe.height = iframeMessage.data.height + 'px';
}, false);

/**
 * Called when bot is clicked and dragged
 *
 * @param {Object} event - Dragging bot event.
 */
function botDragHandler(event) {
  // Do not allow a bot to be moved if they are only in 1 column
  if (botsLeft.offsetWidth !== botsContainer.offsetWidth &&
    _movingContent.length) {
    botsContainerColumns.forEach((c) => {
      c.className = 'slds-col slds-large-size--1-of-3' +
        ' slds-size--1-of-1 col-dragging';
    });

    // Setting data to be transferred
    event.dataTransfer.setData('text', event.target.id);
  } else {
    event.preventDefault();
  }
}

/**
 * Called when bot is able to be dropped
 *
 * @param {Object} event - Dragging bot event.
 * @param {DOM} bot - Bot container.
 */
function allowBotDropHandler(event, bot) {
  event.preventDefault();
  const col = bot.parentElement;
  col.insertBefore(placeholderBot, bot);
}

// Resets all columns back to their initial state
function resetColumns() {
  botsContainerColumns.forEach((c) => {
    c.className = 'slds-col slds-large-size--1-of-3 slds-size--1-of-1';
  });

  if (placeholderBot.parentElement) {
    placeholderBot.parentElement.removeChild(placeholderBot);
  }

  if (blankBot.parentElement) {
    blankBot.parentElement.removeChild(blankBot);
  }

  // Resetting variable as no bots are being moved
  _movingContent = null;
}

/**
 * Called when bot is dropped in a valid column.
 *
 * @param {Object} event - Dropping bot event.
 * @param {DOM} col - Column that bot is being dragged over.
 */
function drop(event, col) {
  event.preventDefault();
  const data = event.dataTransfer.getData('text');
  col.insertBefore(document.getElementById(data), placeholderBot);
  const botIframe = document.getElementById(data)
    .getElementsByTagName('iframe')[ZERO];
  const botIframedoc = botIframe.contentDocument;

  if (_movingContent) {
    uPage.writeInIframedoc(botIframedoc, _movingContent);
  }

  resetColumns();
}

/**
 * Decides which column to place the bot in.
 *
 * @param {Int} botName - Name of the bot
 * @param {Int} botIndex - Index of the bot
 * @returns {String} - L, M or R
 */
function decideBotPosition(botName, botIndex) {
  // A bot layout was defined in room/roomType settings
  if (_botsLayout) {
    if (_botsLayout.leftColumn && _botsLayout.leftColumn.includes(botName)) {
      return 'L';
    } else if (_botsLayout.middleColumn &&
      _botsLayout.middleColumn.includes(botName)) {
      return 'M';
    } else if (_botsLayout.rightColumn &&
      _botsLayout.rightColumn.includes(botName)) {
      return 'R';
    }

    return '';
  }

  // No bot layout was defined in settings
  if ((botIndex + ONE) % THREE === ONE) {
    return 'L';
  } else if ((botIndex + ONE) % THREE === TWO) {
    return 'M';
  }

  return 'R';
}

/**
 * Sets up the bots to be movable between columns.
 *
 * @param {DOM} botContainer - Container of bot
 * @param {Int} botName - Name of the bot
 * @param {Int} botIndex - Index of the bot
 */
function setupMovableBots(botContainer, botName, botIndex) {
  botContainer.setAttribute(
    'draggable',
    'true'
  );

  botContainer.addEventListener('dragstart', (e) => {
    botDragHandler(e);
  });

  botContainer.addEventListener('dragend', () => {
    resetColumns();
  });

  botContainer.addEventListener('dragover', (e) => {
    allowBotDropHandler(e, botContainer);
  });

  // Only need to move the bot if the header is clicked
  botContainer.addEventListener('mousedown', (e) => {
    // The bot header was clicked on
    if (e.target.id === 'title-header') {
      const iframe = botContainer.getElementsByTagName('iframe')[ZERO];
      if (iframe.contentDocument) {
        _movingContent = iframe.contentDocument.head.innerHTML +
          iframe.contentDocument.body.innerHTML;
      } else if (iframe.contentWindow) {
        _movingContent = iframe.contentWindow.document.head.innerHTML +
          iframe.contentWindow.document.body.innerHTML;
      }
    }
  });

  const position = decideBotPosition(botName, botIndex);

  if (position === 'L') {
    botsLeft.appendChild(botContainer);
  } else if (position === 'M') {
    botsMiddle.appendChild(botContainer);
  } else if (position === 'R') {
    botsRight.appendChild(botContainer);
  }
}

/**
 * Creates the minimize button for each bot
 *
 * @param {Object} bot - Bot response with UI
 * @returns {DOM} section - Minimize Button
 */
function createMinimizeButton(bot) {
  const minimize = document.createElement('div');
  minimize.className = 'slds-float_right';
  minimize.style.cursor = 'pointer';
  minimize.onclick = () => {
    const botFrame = document.getElementById(bot.name + '-iframe-section');
    const up = document.getElementById(bot.name + '-toggleUp');
    const down = document.getElementById(bot.name + '-toggleDown');
    if (botFrame.style.display === 'none') {
      up.style.display = 'block';
      down.style.display = 'none';
      botFrame.style.display = 'block';
    } else {
      up.style.display = 'none';
      down.style.display = 'block';
      botFrame.style.display = 'none';
    }
  };

  const svgURL = 'http://www.w3.org/2000/svg';
  const xlinkURL = 'http://www.w3.org/1999/xlink';
  const svgElem = document.createElementNS(svgURL, 'svg');
  svgElem.setAttribute('class',
    'slds-icon slds-icon_xx-small slds-m-top_x-small');
  svgElem.setAttribute('style', 'fill:black');
  const useElemUp = document.createElementNS(svgURL, 'use');
  useElemUp.setAttributeNS(
    xlinkURL,
    'xlink:href',
    '../static/icons/utility-sprite/svg/symbols.svg#chevronup'
  );
  useElemUp.setAttribute('id', bot.name + '-toggleUp');
  svgElem.appendChild(useElemUp);
  const useElemDown = document.createElementNS(svgURL, 'use');
  useElemDown.setAttributeNS(
    xlinkURL,
    'xlink:href',
    '../static/icons/utility-sprite/svg/symbols.svg#chevrondown'
  );
  useElemDown.setAttribute('id', bot.name + '-toggleDown');
  useElemDown.setAttribute('style', 'display: none');
  svgElem.appendChild(useElemDown);
  minimize.appendChild(svgElem);

  return minimize;
}

/**
 * Creates headers for each bot added to the UI
 *
 * @param {Object} bot - Bot response with UI
 * @returns {DOM} section - Header section
 */
function createHeader(bot) {
  const section = document.createElement('div');
  section.className = 'slds-section slds-is-open';

  section.setAttribute(
    'style',
    'box-shadow:0px 0px 20px 2px #e2e2e2;margin:1rem;'
  );

  const title = document.createElement('div');
  title.className =
    'slds-p-horizontal_small ' +
    'slds-theme_shade ';

  const text = document.createElement('h3');
  text.id = 'title-header';
  text.className =
    'slds-section__title ';
  text.innerHTML = bot.name;
  text.style.cursor = 'move';

  const circle = document.createElement('div');
  if (bot.active) {
    circle.setAttribute(
      'style',
      'background:#04844b;width:8px;height:8px;border-radius:50%;margin:5px;'
    );
  } else {
    circle.setAttribute(
      'style',
      'background:#c23934;width:8px;height:8px;border-radius:50%;margin:5px;'
    );
  }
  circle.className = 'slds-float_right';

  title.appendChild(createMinimizeButton(bot));
  title.appendChild(text);
  text.appendChild(circle);
  section.appendChild(title);

  return section;
}

/**
 * Creates footers for each bot added to the UI
 *
 * @param {Object} bot - Bot response with UI
 * @returns {DOM} footer - Footer section
 */
function createFooter(bot) {
  const footer = document.createElement('h3');
  const linkedElement = document.createElement('a');
  const gitHubImage = document.createElement('img');
  const botVersion = document.createElement('span');

  footer.className =
    'slds-section__title ' +
    'slds-p-horizontal_small ' +
    'slds-theme_shade ';

  botVersion.innerHTML = 'Version ' + bot.version;
  botVersion.className = 'slds-p-horizontal--medium';
  linkedElement.href = bot.url;
  linkedElement.target = '_blank';
  linkedElement.rel = 'noopener noreferrer';
  gitHubImage.height = '20';
  gitHubImage.width = '20';
  gitHubImage.src = GITHUB_LOGO;
  linkedElement.appendChild(gitHubImage);
  footer.appendChild(linkedElement);
  footer.appendChild(botVersion);

  return footer;
}

/**
 * Dispatches javascript events to bots in iFrames
 *
 * @param {HTMLObject} iframe - iFrame DOM to add Bot UI
 * @param {Object} bot - Bot to add to frame
 * @param {Object} parsedBot - Object that has the HTML and JS as
 *  HTMLObject stored as key value pair
 * @param {Object} currentUser - The current user
 */
function iframeBot(iframe, bot, parsedBot, currentUser) {
  const botScript = parsedBot.js ? parsedBot.js.innerHTML : '';
  const contentSection = parsedBot.html ? parsedBot.html.innerHTML : '';
  let iframedoc = iframe.document;
  if (iframe.contentDocument) {
    iframedoc = iframe.contentDocument;
  } else if (iframe.contentWindow) {
    iframedoc = iframe.contentWindow.document;
  }

  const iframeCss =
    `<link
      rel="stylesheet"
      type="text/css"
      href="/static/css/salesforce-lightning-design-system.2.4.3.min.css">`;

  const iframeJS =
  `
  <script>
    function outputSize() {
      const el = document.getElementById("${bot.name}");
      // Sometimes clientHeight was > scrollHeight so we need to get the max
      const botHeight =  Math.max(el.clientHeight, el.scrollHeight);
      parent.postMessage(
        {
          "name": "${bot.name}",
          "height": Math.max(botHeight, "${MIN_BOT_HEIGHT}")
        }, "*"
      );
    }

    outputSize();
    if( navigator.userAgent.toLowerCase().indexOf('chrome') > -1 ){
      new ResizeObserver(
        function(event) {
          outputSize();
        }
      ).observe(document.getElementById("${bot.name}"));
    } else {
      var targetNode = document.getElementById("${bot.name}");
      var config = { attributes: true, childList: true };
      var observer = new MutationObserver(function(mutationsList) {
        for(var mutation of mutationsList) {
          outputSize();
        }
      });
      observer.observe(targetNode, config);
      window.addEventListener("resize",
        function(event) {
          outputSize();
        }
      );
    }
  </script>`;

  const iframeContent = iframeCss +
      `<script>var user = "${currentUser}"</script>
      ${contentSection}
      <script>${botScript}</script>` +
      iframeJS;

  uPage.writeInIframedoc(iframedoc, iframeContent);
}

/**
 * Create DOM elements for each of the files in the bots zip.
 *
 * @param {Object} bot - Bot response with UI
 * @param {Int} botIndex - Index of Bot
 * @returns {Object} - An object that stores the HTML dom and
 *   javascript dom as key value pairs
 */
function parseBot(bot) {
  const botScript = document.createElement('script');
  botScript.id = bot.name + '-script';

  const contentSection = document.createElement('div');
  contentSection.className = 'slds-section__content';

  try {
    // Unzip bots
    const zip = new AdmZip(new Buffer(bot.ui.data));
    const zipEntries = zip.getEntries();

    // 'index.html' contains root elements that scripts hook up to
    // and needs to be loaded into the DOM first
    const index = zipEntries.filter((entry) => entry.name === 'index.html');
    if (index.length > ZERO) {
      contentSection.innerHTML = zip.readAsText(index[ZERO]);
      const elem = contentSection
        .querySelector('script[src="/index_bundle.js"]');
      if (elem) {
        elem.parentNode.removeChild(elem);
      }
    }

    // go through zipEntries that arent 'index.html'
    zipEntries.filter(
      (entry) => entry.name !== 'index.html'
    ).forEach((script) => {
      botScript.appendChild(
        document.createTextNode(zip.readAsText(script))
      );
    });

    const output = {
      html: contentSection,
      js: botScript,
    };
    return output;
  } catch (exception) {
    debugMessage('Parse Bot failed', exception);
    return {};
  }
} // parseBots

/**
 * Create and fills DOM containers for bots and adds them to
 * to the page.
 *
 * @param {Object} bot - Bot response with UI
 * @param {Int} botIndex - Index of Bot
 */
function displayBot(bot, botIndex) {
  // Get the bots section of the page
  const botContainer = document.createElement('div');
  botContainer.id = bot.name + '-section';
  const headerSection = createHeader(bot);
  const footerSection = createFooter(bot);

  const iframe = document.createElement('iframe');
  iframe.id = bot.name + '-iframe-section';
  iframe.style.display = 'block';
  iframe.style.border = 'none';
  iframe.style.width = '100%';
  // We don't want to scroll the iframe, just the bot inside it
  iframe.scrolling = 'no';
  iframe.frameBorder = 0;

  headerSection.appendChild(iframe);
  headerSection.appendChild(footerSection);
  botContainer.appendChild(headerSection);
  setupMovableBots(botContainer, bot.name, botIndex);
  const parsedBot = parseBot(bot);

  // user is defined in ./index.pug
  iframeBot(iframe, bot, parsedBot, user);
}
{'fraction_non_alphanumeric': 0.13824273462084286, 'fraction_numerical': 0.006447831184056272, 'mean_word_length': 3.512110244988864, 'pattern_counts': {'":': 8, '<': 19, '<?xml version=': 0, '>': 44, 'https://': 2, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '304439', 'n_tokens_mistral': 10354, 'n_tokens_neox': 9995, 'n_words': 2420}