Refactor extension to webpack

This change is the biggest in the history of the project. The entire
project has been refactored so it is built with webpack.

This involves:
- Creating webpack and npm config files.
- Fixing some bugs in the code due to the fact that webpack uses strict
mode.
- Merging some pieces of code which were shared throughout the codebase
(not exhaustive, more work should be done in this direction).
- Splitting the console_inject.js file into separate files (it had 1000+
lines).
- Adapting all the build-related files (Makefile, bash scripts, etc.)
- Changing the docs to explain the new build process.
- Changing the Zuul playbook/roles to adapt to the new build process.

Change-Id: I16476d47825461c3a318b3f1a1eddb06b2df2e89
diff --git a/src/contentScripts/communityConsole/batchLock.js b/src/contentScripts/communityConsole/batchLock.js
new file mode 100644
index 0000000..5bc0361
--- /dev/null
+++ b/src/contentScripts/communityConsole/batchLock.js
@@ -0,0 +1,133 @@
+import {removeChildNodes, createExtBadge} from './utils.js';
+
+export function nodeIsReadToggleBtn(node) {
+  return ('tagName' in node) && node.tagName == 'MATERIAL-BUTTON' &&
+      node.getAttribute('debugid') !== null &&
+      (node.getAttribute('debugid') == 'mark-read-button' ||
+       node.getAttribute('debugid') == 'mark-unread-button') &&
+      ('parentNode' in node) && node.parentNode !== null &&
+      ('parentNode' in node.parentNode) &&
+      node.parentNode.querySelector('[debugid="batchlock"]') === null &&
+      node.parentNode.parentNode !== null &&
+      ('tagName' in node.parentNode.parentNode) &&
+      node.parentNode.parentNode.tagName == 'EC-BULK-ACTIONS';
+}
+
+export function addBatchLockBtn(readToggle) {
+  var clone = readToggle.cloneNode(true);
+  clone.setAttribute('debugid', 'batchlock');
+  clone.classList.add('TWPT-btn--with-badge');
+  clone.setAttribute('title', chrome.i18n.getMessage('inject_lockbtn'));
+  clone.querySelector('material-icon').setAttribute('icon', 'lock');
+  clone.querySelector('i.material-icon-i').textContent = 'lock';
+
+  var badge = createExtBadge();
+  clone.append(badge);
+
+  clone.addEventListener('click', function() {
+    var modal = document.querySelector('.pane[pane-id="default-1"]');
+
+    var dialog = document.createElement('material-dialog');
+    dialog.setAttribute('role', 'dialog');
+    dialog.setAttribute('aria-modal', 'true');
+    dialog.classList.add('TWPT-dialog');
+
+    var header = document.createElement('header');
+    header.setAttribute('role', 'presentation');
+    header.classList.add('TWPT-dialog-header');
+
+    var title = document.createElement('div');
+    title.classList.add('TWPT-dialog-header--title', 'title');
+    title.textContent = chrome.i18n.getMessage('inject_lockbtn');
+
+    header.append(title);
+
+    var main = document.createElement('main');
+    main.setAttribute('role', 'presentation');
+    main.classList.add('TWPT-dialog-main');
+
+    var p = document.createElement('p');
+    p.textContent = chrome.i18n.getMessage('inject_lockdialog_desc');
+
+    main.append(p);
+
+    dialog.append(header, main);
+
+    var footers = [['lock', 'unlock', 'cancel'], ['reload', 'close']];
+
+    for (var i = 0; i < footers.length; ++i) {
+      var footer = document.createElement('footer');
+      footer.setAttribute('role', 'presentation');
+      footer.classList.add('TWPT-dialog-footer');
+      footer.setAttribute('data-footer-id', i);
+
+      if (i > 0) footer.classList.add('is-hidden');
+
+      footers[i].forEach(action => {
+        var btn = document.createElement('material-button');
+        btn.setAttribute('role', 'button');
+        btn.classList.add('TWPT-dialog-footer-btn');
+        if (i == 1) btn.classList.add('is-disabled');
+
+        switch (action) {
+          case 'lock':
+          case 'unlock':
+            btn.addEventListener('click', _ => {
+              if (btn.classList.contains('is-disabled')) return;
+              var message = {
+                action,
+                prefix: 'TWPT-batchlock',
+              };
+              window.postMessage(message, '*');
+            });
+            break;
+
+          case 'cancel':
+          case 'close':
+            btn.addEventListener('click', _ => {
+              if (btn.classList.contains('is-disabled')) return;
+              modal.classList.remove('visible');
+              modal.style.display = 'none';
+              removeChildNodes(modal);
+            });
+            break;
+
+          case 'reload':
+            btn.addEventListener('click', _ => {
+              if (btn.classList.contains('is-disabled')) return;
+              window.location.reload()
+            });
+            break;
+        }
+
+        var content = document.createElement('div');
+        content.classList.add('content', 'TWPT-dialog-footer-btn--content');
+        content.textContent =
+            chrome.i18n.getMessage('inject_lockdialog_btn_' + action);
+
+        btn.append(content);
+        footer.append(btn);
+      });
+
+      var clear = document.createElement('div');
+      clear.style.clear = 'both';
+
+      footer.append(clear);
+      dialog.append(footer);
+    }
+
+    removeChildNodes(modal);
+    modal.append(dialog);
+    modal.classList.add('visible', 'modal');
+    modal.style.display = 'flex';
+  });
+
+  var duplicateBtn =
+      readToggle.parentNode.querySelector('[debugid="mark-duplicate-button"]');
+  if (duplicateBtn)
+    duplicateBtn.parentNode.insertBefore(
+        clone, (duplicateBtn.nextSibling || duplicateBtn));
+  else
+    readToggle.parentNode.insertBefore(
+        clone, (readToggle.nextSibling || readToggle));
+}