Flatten threads: add UI components to messages
- A quote is added to messages to show which was the previous message in
the reply chain.
- A "reply" button is added to messages to allow users to compose a
comment which is added at the end of the reply chain.
- A bug is fixed in calculating the parent reply ID in the extra info
object. Now parent reply means the first message in a reply chain,
while previous reply means the previous message in the reply chain.
Bug: twpowertools:153
Change-Id: I699507ade52e80287dd634e61f835d53af6a904d
diff --git a/src/contentScripts/communityConsole/flattenThreads/components/ReplyButton.js b/src/contentScripts/communityConsole/flattenThreads/components/ReplyButton.js
new file mode 100644
index 0000000..681c433
--- /dev/null
+++ b/src/contentScripts/communityConsole/flattenThreads/components/ReplyButton.js
@@ -0,0 +1,99 @@
+import '@material/web/button/outlined-button.js';
+
+import {css, html, LitElement} from 'lit';
+import {waitFor} from 'poll-until-promise';
+
+import {SHARED_MD3_STYLES} from '../../../../common/styles/md3.js';
+import {getExtraInfoNodes} from '../flattenThreads.js';
+
+export default class TwptFlattenThreadReplyButton extends LitElement {
+ static properties = {
+ extraInfo: {type: Object},
+ };
+
+ static styles = [
+ SHARED_MD3_STYLES,
+ css`
+ md-outlined-button {
+ --md-outlined-button-container-shape: 0.25rem;
+ --md-outlined-button-container-height: 38px;
+ }
+ `,
+ ];
+
+ constructor() {
+ super();
+ this.extraInfo = {};
+ }
+
+ render() {
+ return html`
+ <md-outlined-button
+ label="Reply"
+ @click=${this.openReplyEditor}>
+ </md-outlined-button>
+ `;
+ }
+
+ #defaultReply(messagePayload) {
+ const quoteHeader = document.createElement('div');
+ const italics = document.createElement('i');
+ italics.textContent = this.extraInfo?.authorName + ' said:';
+ quoteHeader.append(italics);
+
+ const quote = document.createElement('blockquote');
+ quote.innerHTML = messagePayload;
+ getExtraInfoNodes(quote)?.forEach?.(node => {
+ node.parentNode.removeChild(node);
+ });
+
+ const br1 = document.createElement('br');
+ const br2 = document.createElement('br');
+
+ return [quoteHeader, quote, br1, br2];
+ }
+
+ openReplyEditor() {
+ const messageId = this.extraInfo?.id;
+ const messagePayload = document.querySelector(
+ '[data-twpt-message-id="' + messageId +
+ '"] .scTailwindThreadPostcontentroot html-blob');
+ if (!messagePayload) {
+ console.error('[flattenThreads] Payload not found.');
+ return;
+ }
+
+ const parentId = this.extraInfo?.parentId ?? this.extraInfo?.id;
+ const parentNodeReply =
+ document.querySelector('[data-twpt-message-id="' + parentId + '"]')
+ ?.closest?.('sc-tailwind-thread-message-message-card');
+ const parentNodeReplyButton = parentNodeReply?.querySelector?.(
+ '.scTailwindThreadMessageMessagecardadd-comment button');
+ if (!parentNodeReplyButton) {
+ // This is not critical: the reply button might already have been clicked
+ // (so it no longer exists), or the thread might be locked so replying is
+ // disabled and the button does'nt exist.
+ console.debug('[flattenThreads] Reply button not found.');
+ return;
+ }
+
+ // Click the reply button.
+ parentNodeReplyButton.click();
+
+ // Fill in the default reply text (it includes a quote of the message the
+ // user wishes to reply to).
+ waitFor(() => {
+ const editor =
+ parentNodeReply?.querySelector('sc-tailwind-thread-reply-editor');
+ if (editor) return Promise.resolve(editor);
+ return Promise.reject(new Error('Editor not found.'));
+ }, {interval: 75, timeout: 10 * 1000}).then(editor => {
+ const payload =
+ editor?.querySelector('.scTailwindSharedRichtexteditoreditor');
+
+ payload.prepend(...this.#defaultReply(messagePayload.innerHTML));
+ });
+ }
+}
+window.customElements.define(
+ 'twpt-flatten-thread-reply-button', TwptFlattenThreadReplyButton);