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/injections/batchLock.js b/src/injections/batchLock.js
new file mode 100644
index 0000000..9a91203
--- /dev/null
+++ b/src/injections/batchLock.js
@@ -0,0 +1,182 @@
+import {parseUrl} from '../common/commonUtils.js';
+import {getAuthUser} from '../common/communityConsoleUtils.js';
+
+function recursiveParentElement(el, tag) {
+ while (el !== document.documentElement) {
+ el = el.parentNode;
+ if (el.tagName == tag) return el;
+ }
+ return undefined;
+}
+
+// Source:
+// https://stackoverflow.com/questions/33063774/communication-from-an-injected-script-to-the-content-script-with-a-response
+var contentScriptRequest = (function() {
+ var requestId = 0;
+ var prefix = 'TWPT-batchlock-generic';
+
+ function sendRequest(data) {
+ var id = requestId++;
+
+ return new Promise(function(resolve, reject) {
+ var listener = function(evt) {
+ if (evt.source === window && evt.data && evt.data.prefix === prefix &&
+ evt.data.requestId == id) {
+ // Deregister self
+ window.removeEventListener('message', listener);
+ resolve(evt.data.data);
+ }
+ };
+
+ window.addEventListener('message', listener);
+
+ var payload = {data, id, prefix};
+
+ window.dispatchEvent(
+ new CustomEvent('TWPT_sendRequest', {detail: payload}));
+ });
+ }
+
+ return {sendRequest: sendRequest};
+})();
+
+function enableEndButtons() {
+ var buttons = document.querySelectorAll(
+ '.pane[pane-id="default-1"] footer[data-footer-id="1"] material-button');
+ buttons.forEach(btn => {
+ btn.classList.remove('is-disabled');
+ });
+}
+
+function addLogEntry(success, action, url, threadId, errDetails = null) {
+ var p1 = contentScriptRequest.sendRequest({
+ action: 'geti18nMessage',
+ msg: 'inject_lockdialog_log_entry_beginning',
+ placeholders: [threadId],
+ });
+
+ var p2 = contentScriptRequest.sendRequest({
+ action: 'geti18nMessage',
+ msg: 'inject_lockdialog_log_entry_' + (success ? 'success' : 'error') +
+ '_' + action,
+ placeholders: [errDetails],
+ });
+
+ Promise.all([p1, p2]).then(strings => {
+ var log = document.getElementById('TWPT-lock-log');
+ var logEntry = document.createElement('p');
+ logEntry.classList.add(
+ 'TWPT-log-entry', 'TWPT-log-entry--' + (success ? 'success' : 'error'));
+
+ var a = document.createElement('a');
+ a.href = url;
+ a.target = '_blank';
+ a.textContent = strings[0];
+
+ var end = document.createTextNode(': ' + strings[1]);
+
+ logEntry.append(a, end);
+ log.append(logEntry);
+ });
+}
+
+function lockThreads(action) {
+ var modal = document.querySelector('.pane[pane-id="default-1"]');
+ modal.querySelector('footer[data-footer-id="0"]').classList.add('is-hidden');
+ modal.querySelector('footer[data-footer-id="1"]')
+ .classList.remove('is-hidden');
+
+ var checkboxes = document.querySelectorAll(
+ '.thread-group material-checkbox[aria-checked="true"]');
+
+ var p = document.createElement('p');
+ p.style.textAlign = 'center';
+
+ var progress = document.createElement('progress');
+ progress.max = checkboxes.length;
+ progress.value = 0;
+
+ p.append(progress);
+
+ var log = document.createElement('div');
+ log.id = 'TWPT-lock-log';
+ log.classList.add('TWPT-log');
+
+ modal.querySelector('main').textContent = '';
+ modal.querySelector('main').append(p, log);
+
+ var authuser = getAuthUser();
+ var APIRequestUrl =
+ 'https://support.google.com/s/community/api/SetThreadAttribute' +
+ (authuser == '0' ? '' : '?authuser=' + encodeURIComponent(authuser));
+
+ checkboxes.forEach(checkbox => {
+ var url = recursiveParentElement(checkbox, 'A').href;
+ var thread = parseUrl(url);
+ if (thread === false) {
+ console.error('Fatal error: thread URL ' + url + ' could not be parsed.');
+ return;
+ }
+ fetch(APIRequestUrl, {
+ 'headers': {
+ 'content-type': 'text/plain; charset=utf-8',
+ },
+ 'body': JSON.stringify({
+ 1: thread.forum,
+ 2: thread.thread,
+ 3: (action == 'lock' ? 1 : 2),
+ }),
+ 'method': 'POST',
+ 'mode': 'cors',
+ 'credentials': 'include',
+ })
+ .then(res => {
+ if (res.status == 200 || res.status == 400) {
+ return res.json().then(data => ({
+ status: res.status,
+ body: data,
+ }));
+ } else {
+ throw new Error('Status code ' + res.status + ' was not expected.');
+ }
+ })
+ .then(res => {
+ if (res.status == 400) {
+ throw new Error(
+ res.body[4] ||
+ ('Response status: 400. Error code: ' + res.body[2]));
+ }
+ })
+ .then(_ => {
+ addLogEntry(true, action, url, thread.thread);
+ })
+ .catch(err => {
+ console.error(
+ 'An error occurred while locking thread ' + url + ': ' + err);
+ addLogEntry(false, action, url, thread.thread, err);
+ })
+ .then(_ => {
+ progress.value = parseInt(progress.value) + 1;
+ if (progress.value == progress.getAttribute('max'))
+ enableEndButtons();
+ });
+ });
+}
+
+window.addEventListener('message', e => {
+ if (e.source === window && e.data && e.data.prefix === 'TWPT-batchlock' &&
+ e.data.action) {
+ switch (e.data.action) {
+ case 'lock':
+ case 'unlock':
+ console.info('Performing action ' + e.data.action);
+ lockThreads(e.data.action);
+ break;
+
+ default:
+ console.error(
+ 'Action \'' + e.data.action +
+ '\' unknown to TWPT-batchlock receiver.');
+ }
+ }
+});