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/avatars.js b/src/contentScripts/communityConsole/avatars.js
new file mode 100644
index 0000000..253fe8f
--- /dev/null
+++ b/src/contentScripts/communityConsole/avatars.js
@@ -0,0 +1,178 @@
+import {CCApi} from '../../common/api.js';
+import {parseUrl} from '../../common/commonUtils.js';
+
+export var avatars = {
+ isFilterSetUp: false,
+ privateForums: [],
+
+ // Gets a list of private forums. If it is already cached, the cached list is
+ // returned; otherwise it is also computed and cached.
+ getPrivateForums() {
+ return new Promise((resolve, reject) => {
+ if (this.isFilterSetUp) return resolve(this.privateForums);
+
+ if (!document.documentElement.hasAttribute('data-startup'))
+ return reject('[threadListAvatars] Couldn\'t get startup data.');
+
+ var startupData =
+ JSON.parse(document.documentElement.getAttribute('data-startup'));
+ var forums = startupData?.['1']?.['2'];
+ if (forums === undefined)
+ return reject(
+ '[threadListAvatars] Couldn\'t retrieve forums from startup data.');
+
+ for (var f of forums) {
+ var forumId = f?.['2']?.['1']?.['1'];
+ var forumVisibility = f?.['2']?.['18'];
+ if (forumId === undefined || forumVisibility === undefined) {
+ console.warn(
+ '[threadListAvatars] Coudln\'t retrieve forum id and/or forum visibility for the following forum:',
+ f);
+ continue;
+ }
+
+ // forumVisibility's value 1 means "PUBLIC".
+ if (forumVisibility != 1) this.privateForums.push(forumId);
+ }
+
+ // Forum 51488989 is marked as public but it is in fact private.
+ this.privateForums.push('51488989');
+
+ this.isFilterSetUp = true;
+ return resolve(this.privateForums);
+ });
+ },
+
+ // Some threads belong to private forums, and this feature will not be able to
+ // get its avatars since it makes an anonymomus call to get the contents of
+ // the thread.
+ //
+ // This function returns whether avatars should be retrieved depending on if
+ // the thread belongs to a known private forum.
+ shouldRetrieveAvatars(thread) {
+ return this.getPrivateForums().then(privateForums => {
+ return !privateForums.includes(thread.forum);
+ });
+ },
+
+ // Get an object with the author of the thread and an array of the first |num|
+ // replies from the thread |thread|.
+ getFirstMessages(thread, num = 15) {
+ return CCApi(
+ 'ViewThread', {
+ 1: thread.forum,
+ 2: thread.thread,
+ // options
+ 3: {
+ // pagination
+ 1: {
+ 2: num, // maxNum
+ },
+ 3: true, // withMessages
+ 5: true, // withUserProfile
+ 10: false, // withPromotedMessages
+ 16: false, // withThreadNotes
+ 18: true, // sendNewThreadIfMoved
+ }
+ },
+ // |authentication| is false because otherwise this would mark
+ // the thread as read as a side effect, and that would mark all
+ // threads in the list as read.
+ //
+ // Due to the fact that we have to call this endpoint
+ // anonymously, this means we can't retrieve information about
+ // threads in private forums.
+ /* authentication = */ false)
+ .then(data => {
+ var numMessages = data?.['1']?.['8'];
+ if (numMessages === undefined)
+ throw new Error(
+ 'Request to view thread doesn\'t include the number of messages');
+
+ var messages = numMessages == 0 ? [] : data?.['1']['3'];
+ if (messages === undefined)
+ throw new Error(
+ 'numMessages was ' + numMessages +
+ ' but the response didn\'t include any message.');
+
+ var author = data?.['1']?.['4'];
+ if (author === undefined)
+ throw new Error(
+ 'Author isn\'t included in the ViewThread response.');
+
+ return {
+ messages,
+ author,
+ };
+ });
+ },
+
+ // Get a list of at most |num| avatars for thread |thread|
+ getVisibleAvatars(thread, num = 3) {
+ return this.shouldRetrieveAvatars(thread).then(shouldRetrieve => {
+ if (!shouldRetrieve) {
+ console.debug('[threadListAvatars] Skipping thread', thread);
+ return [];
+ }
+
+ return this.getFirstMessages(thread).then(result => {
+ var messages = result.messages;
+ var author = result.author;
+
+ var avatarUrls = [];
+
+ var authorUrl = author?.['1']?.['2'];
+ if (authorUrl !== undefined) avatarUrls.push(authorUrl);
+
+ for (var m of messages) {
+ var url = m?.['3']?.['1']?.['2'];
+
+ if (url === undefined) continue;
+ if (!avatarUrls.includes(url)) avatarUrls.push(url);
+ if (avatarUrls.length == 3) break;
+ }
+
+ return avatarUrls;
+ });
+ });
+ },
+
+ // Inject avatars for thread summary (thread item) |node| in a thread list.
+ inject(node) {
+ var header = node.querySelector(
+ 'ec-thread-summary .main-header .panel-description a.header');
+ if (header === null) {
+ console.error(
+ '[threadListAvatars] Header is not present in the thread item\'s DOM.');
+ return;
+ }
+
+ var thread = parseUrl(header.href);
+ if (thread === false) {
+ console.error('[threadListAvatars] Thread\'s link cannot be parsed.');
+ return;
+ }
+
+ this.getVisibleAvatars(thread)
+ .then(avatarUrls => {
+ var avatarsContainer = document.createElement('div');
+ avatarsContainer.classList.add('TWPT-avatars');
+
+ var count = Math.floor(Math.random() * 4);
+
+ for (var i = 0; i < avatarUrls.length; ++i) {
+ var avatar = document.createElement('div');
+ avatar.classList.add('TWPT-avatar');
+ avatar.style.backgroundImage = 'url(\'' + avatarUrls[i] + '\')';
+ avatarsContainer.appendChild(avatar);
+ }
+
+ header.appendChild(avatarsContainer);
+ })
+ .catch(err => {
+ console.error(
+ '[threadListAvatars] Could not retrieve avatars for thread',
+ thread, err);
+ });
+ },
+};