feat(extra-info): inject extra info in nested replies

Bug: twpowertools:93
Change-Id: Id60030804146981ca3f59a6b2c1d419bbf1f731c
diff --git a/src/contentScripts/communityConsole/extraInfo/index.js b/src/contentScripts/communityConsole/extraInfo/index.js
index 1d2b024..204e7f8 100644
--- a/src/contentScripts/communityConsole/extraInfo/index.js
+++ b/src/contentScripts/communityConsole/extraInfo/index.js
@@ -6,9 +6,10 @@
 import ExpandedThreadListExtraInfoInjection from './injections/expandedThreadList.js';
 import ProfileAbuseExtraInfoInjection from './injections/profileAbuse.js';
 import ProfilePerForumStatsExtraInfoInjection from './injections/profilePerForumStats.js';
+import ThreadCommentExtraInfoInjection from './injections/threadComment.js';
 import ThreadListExtraInfoInjection from './injections/threadList.js';
-import ThreadMessageExtraInfoInjection from './injections/threadMessage.js';
 import ThreadQuestionExtraInfoInjection from './injections/threadQuestion.js';
+import ThreadReplyExtraInfoInjection from './injections/threadReply.js';
 
 export default class ExtraInfo {
   constructor() {
@@ -24,8 +25,10 @@
         profileInfoHandler, optionsWatcher);
     this.threadQuestion =
         new ThreadQuestionExtraInfoInjection(threadInfoHandler, optionsWatcher);
-    this.threadMessage =
-        new ThreadMessageExtraInfoInjection(threadInfoHandler, optionsWatcher);
+    this.threadReply =
+        new ThreadReplyExtraInfoInjection(threadInfoHandler, optionsWatcher);
+    this.threadComment =
+        new ThreadCommentExtraInfoInjection(threadInfoHandler, optionsWatcher);
     this.expandedThreadList = new ExpandedThreadListExtraInfoInjection(
         threadListInfoHandler, optionsWatcher);
     this.threadList =
@@ -55,7 +58,11 @@
     this.threadQuestion.injectIfEnabled({stateChips, isMessageNode: false});
   }
 
-  injectAtMessageIfEnabled(messageNode) {
-    this.threadMessage.injectIfEnabled({messageNode, isMessageNode: true});
+  injectAtReplyIfEnabled(messageNode) {
+    this.threadReply.injectIfEnabled({messageNode, isMessageNode: true});
+  }
+
+  injectAtCommentIfEnabled(messageNode) {
+    this.threadComment.injectIfEnabled({messageNode, isMessageNode: true});
   }
 }
diff --git a/src/contentScripts/communityConsole/extraInfo/injections/threadMessage.js b/src/contentScripts/communityConsole/extraInfo/injections/baseThreadMessage.js
similarity index 65%
rename from src/contentScripts/communityConsole/extraInfo/injections/threadMessage.js
rename to src/contentScripts/communityConsole/extraInfo/injections/baseThreadMessage.js
index e203e9e..d086f9d 100644
--- a/src/contentScripts/communityConsole/extraInfo/injections/threadMessage.js
+++ b/src/contentScripts/communityConsole/extraInfo/injections/baseThreadMessage.js
@@ -1,12 +1,28 @@
 import {MDCTooltip} from '@material/tooltip';
 
+import {shouldImplement} from '../../../../common/commonUtils.js';
 import ThreadModel from '../../../../models/Thread.js';
 import MessageExtraInfoService from '../services/message.js';
 
 import BaseExtraInfoInjection from './base.js';
 
-export default class ThreadMessageExtraInfoInjection extends
+export default class BaseThreadMessageExtraInfoInjection extends
     BaseExtraInfoInjection {
+  /**
+   * The class of the interactions root element.
+   */
+  getInteractionsRootClass() {
+    shouldImplement('getInteractionsRootClass');
+  }
+
+  /**
+   * The class of the interactions root element which signifies that it is
+   * non-empty.
+   */
+  getInteractionsRootNonEmptyClass() {
+    shouldImplement('getInteractionsRootNonEmptyClass');
+  }
+
   inject(threadInfo, injectionDetails) {
     const messageNode = injectionDetails.messageNode;
     const message = this.#getMessage(threadInfo, messageNode);
@@ -22,8 +38,8 @@
   }
 
   #injectChips(chips, messageNode) {
-    const interactionsElement = messageNode.querySelector(
-        '.scTailwindThreadMessageMessageinteractionsroot');
+    const interactionsElement =
+        messageNode.querySelector('.' + this.getInteractionsRootClass());
     if (interactionsElement === null)
       throw new Error(`Couldn't find interactions element.`);
 
@@ -34,7 +50,6 @@
   }
 
   #indicateInteractionsElementIsNonEmpty(interactionsElement) {
-    interactionsElement.classList.add(
-        'scTailwindThreadMessageMessageinteractionsinteractions');
+    interactionsElement.classList.add(this.getInteractionsRootNonEmptyClass());
   }
 }
diff --git a/src/contentScripts/communityConsole/extraInfo/injections/threadComment.js b/src/contentScripts/communityConsole/extraInfo/injections/threadComment.js
new file mode 100644
index 0000000..ed14575
--- /dev/null
+++ b/src/contentScripts/communityConsole/extraInfo/injections/threadComment.js
@@ -0,0 +1,11 @@
+import BaseThreadMessageExtraInfoInjection from './baseThreadMessage.js';
+
+export default class ThreadCommentExtraInfoInjection extends BaseThreadMessageExtraInfoInjection {
+  getInteractionsRootClass() {
+    return 'scTailwindThreadMessageMessageinteractionsroot';
+  }
+
+  getInteractionsRootNonEmptyClass() {
+    return 'scTailwindThreadMessageMessageinteractionsinteractions';
+  }
+}
diff --git a/src/contentScripts/communityConsole/extraInfo/injections/threadReply.js b/src/contentScripts/communityConsole/extraInfo/injections/threadReply.js
new file mode 100644
index 0000000..ed14575
--- /dev/null
+++ b/src/contentScripts/communityConsole/extraInfo/injections/threadReply.js
@@ -0,0 +1,11 @@
+import BaseThreadMessageExtraInfoInjection from './baseThreadMessage.js';
+
+export default class ThreadCommentExtraInfoInjection extends BaseThreadMessageExtraInfoInjection {
+  getInteractionsRootClass() {
+    return 'scTailwindThreadMessageMessageinteractionsroot';
+  }
+
+  getInteractionsRootNonEmptyClass() {
+    return 'scTailwindThreadMessageMessageinteractionsinteractions';
+  }
+}
diff --git a/src/contentScripts/communityConsole/extraInfo/services/message.js b/src/contentScripts/communityConsole/extraInfo/services/message.js
index 3b43403..3ad9798 100644
--- a/src/contentScripts/communityConsole/extraInfo/services/message.js
+++ b/src/contentScripts/communityConsole/extraInfo/services/message.js
@@ -4,9 +4,13 @@
 
 export default class MessageExtraInfoService {
   static getMessageIdFromNode(messageNode) {
-    const id =
-        messageNode.querySelector('.scTailwindThreadMessageMessagecardcontent')
-            ?.getAttribute?.('data-stats-id');
+    const isMainReply =
+        messageNode.tagName == 'SC-TAILWIND-THREAD-MESSAGE-MESSAGE-CARD';
+    const cardContentClass = isMainReply ?
+        '.scTailwindThreadMessageMessagecardcontent' :
+        '.scTailwindThreadMessageCommentcardnested-reply';
+    const id = messageNode.querySelector(cardContentClass)
+                   ?.getAttribute?.('data-stats-id');
     if (id === undefined)
       throw new Error(`Couldn't retrieve message id from node.`);
     return id;
diff --git a/src/contentScripts/communityConsole/main.js b/src/contentScripts/communityConsole/main.js
index 4cd711c..08ff125 100644
--- a/src/contentScripts/communityConsole/main.js
+++ b/src/contentScripts/communityConsole/main.js
@@ -69,6 +69,9 @@
   // Replies (for the extra info feature)
   'sc-tailwind-thread-message-message-list sc-tailwind-thread-message-message-card',
 
+  // Comments (for the extra info feature)
+  'sc-tailwind-thread-message-message-list sc-tailwind-thread-message-comment-card',
+
   // User activity chart (for the per-forum stats feature)
   'ec-unified-user .scTailwindUser_profileUserprofilesection ' +
       'sc-tailwind-shared-activity-chart',
@@ -201,7 +204,12 @@
     }
     if (node.matches(
             'sc-tailwind-thread-message-message-list sc-tailwind-thread-message-message-card')) {
-      window.TWPTExtraInfo.injectAtMessageIfEnabled(node);
+      window.TWPTExtraInfo.injectAtReplyIfEnabled(node);
+    }
+
+    if (node.matches(
+            'sc-tailwind-thread-message-message-list sc-tailwind-thread-message-comment-card')) {
+      window.TWPTExtraInfo.injectAtCommentIfEnabled(node);
     }
 
     // Inject per-forum stats section in the user profile