From cd0d5bd6216d53ddafad93a567769c1486112f64 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Tue, 3 Dec 2024 15:16:20 +0100
Subject: [PATCH 01/23] Add private_message_slide library.

---
 css/private_message_slide.css  | 13 +++++++++
 css/private_message_slide.scss | 15 ++++++++++
 js/private_message_slide.js    | 51 ++++++++++++++++++++++++++++++++++
 private_message.libraries.yml  | 10 +++++++
 4 files changed, 89 insertions(+)
 create mode 100644 css/private_message_slide.css
 create mode 100644 css/private_message_slide.scss
 create mode 100644 js/private_message_slide.js

diff --git a/css/private_message_slide.css b/css/private_message_slide.css
new file mode 100644
index 00000000..0536dac9
--- /dev/null
+++ b/css/private_message_slide.css
@@ -0,0 +1,13 @@
+.private-message-slide {
+  overflow: hidden;
+  transition: height 300ms ease-out, opacity 300ms ease-out;
+}
+.private-message-slide__collapsed {
+  height: 0;
+  opacity: 0;
+}
+.private-message-slide__expanded {
+  height: auto;
+  opacity: 1;
+}
+
diff --git a/css/private_message_slide.scss b/css/private_message_slide.scss
new file mode 100644
index 00000000..a6db65b0
--- /dev/null
+++ b/css/private_message_slide.scss
@@ -0,0 +1,15 @@
+.private-message-slide {
+  overflow: hidden;
+  transition: height 300ms ease-out, opacity 300ms ease-out;
+
+  &__collapsed {
+    height: 0;
+    opacity: 0;
+  }
+
+  &__expanded {
+    height: auto;
+    opacity: 1;
+  }
+
+}
diff --git a/js/private_message_slide.js b/js/private_message_slide.js
new file mode 100644
index 00000000..3de49d42
--- /dev/null
+++ b/js/private_message_slide.js
@@ -0,0 +1,51 @@
+/**
+ * @file
+ *
+ * Slide functions for Private Message module.
+ */
+
+((Drupal) => {
+  Drupal.PrivateMessageSlide = Drupal.PrivateMessageSlide || {};
+
+  /**
+   * Toggles slide-up and slide-down effects.
+   *
+   * @param {HTMLElement} element - The element to toggle.
+   * @param {boolean} expand - True to slide down, false to slide up.
+   */
+  Drupal.PrivateMessageSlide.toggleSlide = (element, expand) => {
+    // Ensure the element is valid
+    if (!element) {
+      return;
+    }
+
+    // Add the base slide class if not already present
+    element.classList.add('private-message-slide');
+
+    if (expand) {
+      // Prepare to slide down
+      element.classList.remove('private-message-slide__collapsed');
+      element.classList.add('private-message-slide__expanded');
+
+      // Dynamically set the height to the element's natural height
+      element.style.height = `${element.scrollHeight}px`;
+
+      // Reset height after transition to prevent issues on resize
+      element.addEventListener(
+        'transitionend',
+        () => {
+          element.style.height = ''; // Clear inline height to use natural height
+        },
+        { once: true },
+      );
+    } else {
+      // Slide up
+      element.style.height = `${element.scrollHeight}px`; // Set the current height explicitly
+      requestAnimationFrame(() => {
+        element.classList.remove('private-message-slide__expanded');
+        element.classList.add('private-message-slide__collapsed');
+        element.style.height = '0'; // Collapse the height
+      });
+    }
+  };
+})(Drupal);
diff --git a/private_message.libraries.yml b/private_message.libraries.yml
index fdc3ed30..458a7b24 100644
--- a/private_message.libraries.yml
+++ b/private_message.libraries.yml
@@ -10,6 +10,7 @@ inbox_block_script:
     - core/once
     - core/drupal.ajax
     - core/drupalSettings
+    - private_message/private_message_slide
 
 inbox_block_style:
   css:
@@ -50,3 +51,12 @@ message_form:
     - core/once
     - core/drupalSettings
     - private_message/browser_notification
+
+private_message_slide:
+  js:
+    js/private_message_slide.js: { }
+  css:
+    theme:
+      css/private_message_slide.css: { }
+  dependencies:
+    - core/drupal
-- 
GitLab


From 9c04c8ccf9c0f0d0858d680a8de1dbe06daad3b9 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Tue, 3 Dec 2024 15:28:05 +0100
Subject: [PATCH 02/23] Fix stylelint issues.

---
 css/private_message_slide.css  | 5 +++--
 css/private_message_slide.scss | 4 +++-
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/css/private_message_slide.css b/css/private_message_slide.css
index 0536dac9..f6380e1d 100644
--- a/css/private_message_slide.css
+++ b/css/private_message_slide.css
@@ -1,6 +1,8 @@
 .private-message-slide {
   overflow: hidden;
-  transition: height 300ms ease-out, opacity 300ms ease-out;
+  transition:
+    height 300ms ease-out,
+    opacity 300ms ease-out;
 }
 .private-message-slide__collapsed {
   height: 0;
@@ -10,4 +12,3 @@
   height: auto;
   opacity: 1;
 }
-
diff --git a/css/private_message_slide.scss b/css/private_message_slide.scss
index a6db65b0..3fcc7df3 100644
--- a/css/private_message_slide.scss
+++ b/css/private_message_slide.scss
@@ -1,6 +1,8 @@
 .private-message-slide {
   overflow: hidden;
-  transition: height 300ms ease-out, opacity 300ms ease-out;
+  transition:
+    height 300ms ease-out,
+    opacity 300ms ease-out;
 
   &__collapsed {
     height: 0;
-- 
GitLab


From 90764a8a79045c25374823bde6f051a4eabd5e30 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Wed, 4 Dec 2024 08:54:41 +0100
Subject: [PATCH 03/23] Add previous functionality for inbox.

---
 js/private_message_inbox_previous.js | 65 ++++++++++++++++++++++++++++
 1 file changed, 65 insertions(+)
 create mode 100644 js/private_message_inbox_previous.js

diff --git a/js/private_message_inbox_previous.js b/js/private_message_inbox_previous.js
new file mode 100644
index 00000000..e4cc2d80
--- /dev/null
+++ b/js/private_message_inbox_previous.js
@@ -0,0 +1,65 @@
+/**
+ * @file
+ * Adds JavaScript functionality to the inbox previous button.
+ */
+
+((Drupal) => {
+
+  /**
+   * Previous functionality.
+   */
+  Drupal.privateMessageInboxPrevious = {
+    wrapperId: 'load-previous-threads-button-wrapper',
+    buttonId: 'load-previous-threads-button',
+
+    /**
+     * Display the previous button.
+     *
+     * @param {HTMLElement} blockElement - The element where the button will be added.
+     * @param {Function} callback - The function to call on button click.
+     */
+    displayButton(blockElement, callback) {
+      blockElement.insertAdjacentHTML(
+        'afterend',
+        `<div id="${this.wrapperId}">
+          <a href="#" id="${this.buttonId}" aria-label="${Drupal.t(
+          'Load previous threads',
+        )}">${Drupal.t('Load Previous')}</a>
+        </div>`,
+      );
+
+      const loadPreviousButton = document.getElementById(this.buttonId);
+      loadPreviousButton.addEventListener('click', callback);
+    },
+
+    /**
+     * Slides done the button.
+     */
+    slideDownButton() {
+      const buttonWrapper = document.getElementById(
+        this.wrapperId,
+      );
+      if (buttonWrapper) {
+        Drupal.PrivateMessageSlide.toggleSlide(buttonWrapper, false);
+      }
+    },
+
+    /*
+     * Detaches event listener.
+     *
+     * @param {Document|HTMLElement} context - The context to search for the button.
+     * @param {Function} callback - The function to detach from the click event.
+     */
+    detachEventListener(context, callback) {
+      const button = context.querySelector('#' + this.buttonId);
+      if (button) {
+        button.removeEventListener(
+          'click',
+          callback,
+        );
+      }
+    },
+  };
+
+})(Drupal);
+
-- 
GitLab


From 9edae5680fd03796a08032e6fde53168e7156844 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Wed, 4 Dec 2024 08:57:53 +0100
Subject: [PATCH 04/23] WIP

---
 js/private_message_inbox_block.js | 402 ++++++++++++++----------------
 private_message.libraries.yml     |   1 +
 2 files changed, 188 insertions(+), 215 deletions(-)

diff --git a/js/private_message_inbox_block.js b/js/private_message_inbox_block.js
index 7516f295..91e48a2a 100644
--- a/js/private_message_inbox_block.js
+++ b/js/private_message_inbox_block.js
@@ -3,271 +3,243 @@
  * Adds JavaScript functionality to the private message inbox block.
  */
 
-Drupal.PrivateMessageInbox = {};
-Drupal.PrivateMessageInbox.updateInbox = {};
-
-(($, Drupal, drupalSettings, window, once) => {
-  let container;
+((Drupal, drupalSettings, window, once) => {
   let updateInterval;
-  let loadingPrev;
-  let loadingNew;
+  let container;
+  let loadingPrevInProgress = false;
+  let loadingNewInProgress = false;
 
   /**
-   * Used to manually trigger Drupal's JavaScript commands.
-   * @param {Object} data The data.
+   * Utility function to create a temporary DOM container.
+   * @param {string} html The HTML string to parse.
+   * @returns {DocumentFragment} Parsed HTML content as a DocumentFragment.
    */
-  function triggerCommands(data) {
-    const ajaxObject = Drupal.ajax({
-      url: '',
-      base: false,
-      element: false,
-      progress: false,
-    });
-
-    // Trigger any ajax commands in the response.
-    ajaxObject.success(data, 'success');
-  }
+  const parseHTML = (html) => {
+    const tempDiv = document.createElement('div');
+    tempDiv.innerHTML = html;
+    return document.createDocumentFragment().appendChild(tempDiv);
+  };
 
   /**
-   * Updates the inbox after an Ajax call.
+   * Private message inbox block functionality.
    */
-  function updateInbox() {
-    if (!loadingNew) {
-      loadingNew = true;
+  Drupal.PrivateMessageInboxBlock = {
+
+    /**
+     * Updates the inbox with new threads.
+     */
+    triggerUpdateInboxCallback() {
+      if (loadingNewInProgress) {
+        return;
+      }
 
+      loadingNewInProgress = true;
       const ids = {};
 
-      if (container.length > 0) {
-        container[0]
-          .querySelectorAll('.private-message-thread-inbox')
-          .forEach((el) => {
-            ids[el.dataset.threadId] = el.dataset.lastUpdate;
-          });
-      }
+      container
+        .querySelectorAll('.private-message-thread-inbox')
+        .forEach((el) => {
+          ids[el.dataset.threadId] = el.dataset.lastUpdate;
+        });
 
-      $.ajax({
+      Drupal.ajax({
         url: drupalSettings.privateMessageInboxBlock.loadNewUrl,
-        method: 'POST',
-        data: { ids },
-        success(data) {
-          loadingNew = false;
-          triggerCommands(data);
+        submit: { ids },
+      })
+        .execute()
+        .always(() => {
+          loadingNewInProgress = false;
+
           if (updateInterval) {
-            window.setTimeout(updateInbox, updateInterval);
+            window.setTimeout(
+              Drupal.PrivateMessageInboxBlock.triggerUpdateInboxCallback,
+              updateInterval,
+            );
           }
-        },
-      });
-    }
-  }
+        });
+    },
 
-  /**
-   * Reorders the inbox after an Ajax Load, to show newest threads first.
-   * @param {Array} threadIds The threads IDs.
-   * @param {Array} newThreads The new Threads.
-   */
-  function reorderInbox(threadIds, newThreads) {
-    const map = {};
+    /**
+     * Appends older threads to the inbox.
+     * @param {string} threads HTML content of threads.
+     */
+    insertPreviousThreads(threads) {
+      const contents = parseHTML(threads).childNodes;
 
-    container[0]
-      .querySelectorAll(':scope > .private-message-thread-inbox')
-      .forEach((el) => {
-        map[el.dataset.threadId] = $(el);
+      contents.forEach((content) => {
+        container.appendChild(content);
       });
 
-    threadIds.forEach((threadId) => {
-      if (newThreads[threadId]) {
-        if (map[threadId]) {
-          map[threadId].remove();
-        }
+      // contents.css('display', 'none').appendTo(container).slideDown(300);
+      // Drupal.attachBehaviors(contents[0]);
+    },
 
-        $('<div/>').html(newThreads[threadId]).contents().appendTo(container);
-      } else if (map[threadId]) {
-        container.append(map[threadId]);
+    /**
+     * Handles loading older threads.
+     * @param {Event} e The click event.
+     */
+    triggerLoadOldThreadCallback(e) {
+      e.preventDefault();
+      if (loadingPrevInProgress) {
+        return;
       }
-    });
-
-    Drupal.attachBehaviors(container[0]);
-  }
-
-  /**
-   * Inserts older threads into the inbox after an Ajax load.
-   * @param {string} threads The threads HTML.
-   */
-  function insertPreviousThreads(threads) {
-    const contents = $('<div/>').html(threads).contents();
 
-    contents.css('display', 'none').appendTo(container).slideDown(300);
-    Drupal.attachBehaviors(contents[0]);
-  }
+      loadingPrevInProgress = true;
 
-  /**
-   * Adds CSS classes to the currently selected thread.
-   * @param {string} threadId The thread id.
-   */
-  function setActiveThread(threadId) {
-    container.find('.active-thread:first').removeClass('active-thread');
-    container
-      .find(`.private-message-thread[data-thread-id="${threadId}"]:first`)
-      .removeClass('unread-thread')
-      .addClass('active-thread');
-  }
-
-  /**
-   * Click handler for the button that loads older threads into the inbox.
-   * @param {Object} e The event.
-   */
-  function loadOldThreadWatcherHandler(e) {
-    e.preventDefault();
-
-    if (!loadingPrev) {
-      loadingPrev = true;
-
-      let oldestTimestamp;
-      container[0].querySelectorAll('.private-message-thread').forEach((el) => {
-        const timestamp = parseInt(el.dataset.lastUpdate, 10);
-        oldestTimestamp = !oldestTimestamp
-          ? timestamp
-          : Math.min(timestamp, oldestTimestamp);
+      let oldestTimestamp = Infinity;
+      container.querySelectorAll('.private-message-thread').forEach((el) => {
+        oldestTimestamp = Math.min(oldestTimestamp, parseInt(el.dataset.lastUpdate, 10));
       });
 
-      $.ajax({
+      Drupal.ajax({
         url: drupalSettings.privateMessageInboxBlock.loadPrevUrl,
-        data: {
+        submit: {
           timestamp: oldestTimestamp,
           count: drupalSettings.privateMessageInboxBlock.threadCount,
         },
-        success(data) {
-          loadingPrev = false;
-          triggerCommands(data);
-        },
+      })
+        .execute()
+        .always(() => {
+          loadingPrevInProgress = false;
+        });
+    },
+
+    /**
+     * Reorders the inbox to show the newest threads first.
+     *
+     * @param {Array} threadIds Thread IDs in the desired order.
+     * @param {Array} newThreads HTML content of new threads keyed by thread ID.
+     */
+    reorderInbox(threadIds, newThreads) {
+      const existingThreads = {};
+
+      container.querySelectorAll(':scope > .private-message-thread-inbox').forEach((el) => {
+        existingThreads[el.dataset.threadId] = el;
       });
-    }
-  }
 
-  /**
-   * Watches the button that loads previous threads into the inbox.
-   * @param {Object} context The context.
-   */
-  function loadOlderThreadWatcher(context) {
-    once(
-      'load-older-threads-watcher',
-      '#load-previous-threads-button',
-      context,
-    ).forEach((el) => {
-      el.addEventListener('click', loadOldThreadWatcherHandler);
-    });
-  }
+      threadIds.forEach((threadId) => {
+        if (newThreads[threadId]) {
+          if (existingThreads[threadId]) {
+            existingThreads[threadId].remove();
+          }
+          const newThreadContent = parseHTML(newThreads[threadId]).childNodes;
+          newThreadContent.forEach((child) => container.appendChild(child));
+        } else if (existingThreads[threadId]) {
+          container.appendChild(existingThreads[threadId]);
+        }
+      });
 
-  /**
-   * Click Handler executed when private message threads are clicked.
-   *
-   * Loads the thread into the private message window.
-   * @param {Event} e The event.
-   */
-  const inboxThreadLinkListenerHandler = (e) => {
-    if (Drupal.PrivateMessages) {
-      e.preventDefault();
-      const threadId = e.currentTarget.dataset.threadId;
-      Drupal.PrivateMessages.loadThread(threadId);
-      setActiveThread(threadId);
-    }
-  };
+      // TODO: it generates js errors
+      // Drupal.attachBehaviors(container);
+    },
 
-  /**
-   * Watches private message threads for clicks, so new threads can be loaded.
-   * @param {Object} context The context.
-   */
-  function inboxThreadLinkListener(context) {
-    once(
-      'inbox-thread-link-listener',
-      '.private-message-inbox-thread-link',
-      context,
-    ).forEach((el) => {
-      el.addEventListener('click', inboxThreadLinkListenerHandler);
-    });
-  }
+    /**
+     * Sets the active thread visually.
+     * @param {string} threadId The thread ID.
+     */
+    setActiveThread(threadId) {
+      const activeThread = container.querySelector('.active-thread');
+      if (activeThread) {
+        activeThread.classList.remove('active-thread');
+      }
+
+      const targetThread = container.querySelector(
+        `.private-message-thread[data-thread-id="${threadId}"]`,
+      );
+      if (targetThread) {
+        targetThread.classList.remove('unread-thread');
+        targetThread.classList.add('active-thread');
+      }
+    },
+  };
 
   /**
-   * Initializes the private message inbox JavaScript.
+   * Attaches the private message inbox block behavior.
    */
-  function init(context) {
-    $(
-      once(
-        'init-inbox-block',
+  Drupal.behaviors.privateMessageInboxBlock = {
+    attach(context) {
+      const containerFormContext = once(
+        'private-message-inbox-block',
         '.block-private-message-inbox-block .private-message-thread--full-container',
         context,
-      ),
-    ).each(function () {
-      container = $(this);
+      ).shift();
 
-      const threadId = container
-        .children('.private-message-thread:first')
-        .attr('data-thread-id');
-      setActiveThread(threadId);
+      if (!containerFormContext) {
+        return;
+      }
+      container = containerFormContext;
+
+      const threadId = container.querySelector('.private-message-thread')?.dataset.threadId;
+      if (threadId) {
+        Drupal.PrivateMessageInboxBlock.setActiveThread(threadId);
+      }
 
       if (
         drupalSettings.privateMessageInboxBlock.totalThreads >
         drupalSettings.privateMessageInboxBlock.itemsToShow
       ) {
-        $('<div/>', { id: 'load-previous-threads-button-wrapper' })
-          .append(
-            $('<a/>', { href: '#', id: 'load-previous-threads-button' }).text(
-              Drupal.t('Load Previous'),
-            ),
-          )
-          .insertAfter(container);
-        loadOlderThreadWatcher(document);
+        Drupal.privateMessageInboxPrevious.displayButton(
+          container,
+          Drupal.PrivateMessageInboxBlock.triggerLoadOldThreadCallback,
+        );
       }
+
       updateInterval =
         drupalSettings.privateMessageInboxBlock.ajaxRefreshRate * 1000;
       if (updateInterval) {
-        window.setTimeout(updateInbox, updateInterval);
+        window.setTimeout(
+          Drupal.PrivateMessageInboxBlock.triggerUpdateInboxCallback,
+          updateInterval,
+        );
       }
-    });
-  }
-
-  Drupal.behaviors.privateMessageInboxBlock = {
-    attach(context) {
-      init(context);
-      inboxThreadLinkListener(context);
-
-      Drupal.AjaxCommands.prototype.insertInboxOldPrivateMessageThreads = (
-        ajax,
-        response,
-      ) => {
-        if (response.threads) {
-          insertPreviousThreads(response.threads);
-        }
-        if (!response.threads || !response.hasNext) {
-          $('#load-previous-threads-button')
-            .parent()
-            .slideUp(300, function () {
-              $(this).remove();
-            });
-        }
-      };
+    },
+    detach(context) {
+      Drupal.privateMessageInboxPrevious.detachEventListener(
+        context,
+        Drupal.PrivateMessageInboxBlock.triggerLoadOldThreadCallback,
+      );
+
+      // Unbind the 'click' event from '.private-message-inbox-thread-link' elements
+      // const inboxThreadLinks = context.querySelectorAll('.private-message-inbox-thread-link');
+      // inboxThreadLinks.forEach((link) => {
+      //   link.removeEventListener('click', inboxThreadLinkListenerHandler);
+      // });
+    },
+  };
 
-      Drupal.AjaxCommands.prototype.privateMessageInboxUpdate = (
-        ajax,
-        response,
-      ) => reorderInbox(response.threadIds, response.newThreads);
+  /**
+   * Custom AJAX command to insert older threads into the inbox.
+   */
+  Drupal.AjaxCommands.prototype.insertInboxOldPrivateMessageThreads = (
+    ajax,
+    response,
+  ) => {
+    if (response.threads) {
+      Drupal.PrivateMessageInboxBlock.insertPreviousThreads(response.threads);
+    }
 
-      Drupal.AjaxCommands.prototype.privateMessageTriggerInboxUpdate = () =>
-        updateInbox();
+    if (!response.threads || !response.hasNext) {
+      Drupal.privateMessageInboxPrevious.slideDownButton();
+    }
+  };
 
-      if (Drupal.PrivateMessages) {
-        Drupal.PrivateMessages.setActiveThread = (id) => setActiveThread(id);
-      }
+  /**
+   * Custom AJAX command to update the inbox with new threads.
+   */
+  Drupal.AjaxCommands.prototype.privateMessageInboxUpdate = (
+    ajax,
+    response,
+  ) => {
+    Drupal.PrivateMessageInboxBlock.reorderInbox(
+      response.threadIds,
+      response.newThreads,
+    );
+  };
 
-      Drupal.PrivateMessageInbox.updateInbox = () => updateInbox();
-    },
-    detach(context) {
-      $(context)
-        .find('#load-previous-threads-button')
-        .unbind('click', loadOldThreadWatcherHandler);
-      $(context)
-        .find('.private-message-inbox-thread-link')
-        .unbind('click', inboxThreadLinkListenerHandler);
-    },
+  /**
+   * Custom AJAX command to trigger an inbox update.
+   */
+  Drupal.AjaxCommands.prototype.privateMessageTriggerInboxUpdate = () => {
+    Drupal.PrivateMessageInboxBlock.triggerUpdateInboxCallback();
   };
-})(jQuery, Drupal, drupalSettings, window, once);
+})(Drupal, drupalSettings, window, once);
diff --git a/private_message.libraries.yml b/private_message.libraries.yml
index 458a7b24..ac605702 100644
--- a/private_message.libraries.yml
+++ b/private_message.libraries.yml
@@ -4,6 +4,7 @@ history_api:
 
 inbox_block_script:
   js:
+    js/private_message_inbox_previous.js: {}
     js/private_message_inbox_block.js: {}
   dependencies:
     - core/jquery
-- 
GitLab


From 8581792faa60c2d8578d69ac7d5600da592bf9b5 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Wed, 4 Dec 2024 09:13:48 +0100
Subject: [PATCH 05/23] setTimeoutForUpdateInboxCallback

---
 js/private_message_inbox_block.js | 33 ++++++++++++++++---------------
 1 file changed, 17 insertions(+), 16 deletions(-)

diff --git a/js/private_message_inbox_block.js b/js/private_message_inbox_block.js
index 91e48a2a..974eed3b 100644
--- a/js/private_message_inbox_block.js
+++ b/js/private_message_inbox_block.js
@@ -4,7 +4,6 @@
  */
 
 ((Drupal, drupalSettings, window, once) => {
-  let updateInterval;
   let container;
   let loadingPrevInProgress = false;
   let loadingNewInProgress = false;
@@ -49,16 +48,25 @@
         .execute()
         .always(() => {
           loadingNewInProgress = false;
-
-          if (updateInterval) {
-            window.setTimeout(
-              Drupal.PrivateMessageInboxBlock.triggerUpdateInboxCallback,
-              updateInterval,
-            );
-          }
+          Drupal.PrivateMessageInboxBlock.setTimeoutForUpdateInboxCallback();
         });
     },
 
+    /**
+     * Sets timeout for update inbox callback.
+     */
+    setTimeoutForUpdateInboxCallback() {
+      let updateInterval =
+        drupalSettings.privateMessageInboxBlock.ajaxRefreshRate * 1000;
+      if (!updateInterval) {
+        return;
+      }
+      window.setTimeout(
+        Drupal.PrivateMessageInboxBlock.triggerUpdateInboxCallback,
+        updateInterval,
+      );
+    },
+
     /**
      * Appends older threads to the inbox.
      * @param {string} threads HTML content of threads.
@@ -184,14 +192,7 @@
         );
       }
 
-      updateInterval =
-        drupalSettings.privateMessageInboxBlock.ajaxRefreshRate * 1000;
-      if (updateInterval) {
-        window.setTimeout(
-          Drupal.PrivateMessageInboxBlock.triggerUpdateInboxCallback,
-          updateInterval,
-        );
-      }
+      Drupal.PrivateMessageInboxBlock.setTimeoutForUpdateInboxCallback();
     },
     detach(context) {
       Drupal.privateMessageInboxPrevious.detachEventListener(
-- 
GitLab


From 0bf8c19c601d0f557d79632ca61f08195bd811d9 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Wed, 4 Dec 2024 10:26:39 +0100
Subject: [PATCH 06/23] Add utils library

---
 js/private_message_inbox_block.js | 15 ++-------------
 js/private_message_utils.js       | 25 +++++++++++++++++++++++++
 private_message.libraries.yml     | 27 +++++++++++++++++----------
 3 files changed, 44 insertions(+), 23 deletions(-)
 create mode 100644 js/private_message_utils.js

diff --git a/js/private_message_inbox_block.js b/js/private_message_inbox_block.js
index 974eed3b..c29989bc 100644
--- a/js/private_message_inbox_block.js
+++ b/js/private_message_inbox_block.js
@@ -8,17 +8,6 @@
   let loadingPrevInProgress = false;
   let loadingNewInProgress = false;
 
-  /**
-   * Utility function to create a temporary DOM container.
-   * @param {string} html The HTML string to parse.
-   * @returns {DocumentFragment} Parsed HTML content as a DocumentFragment.
-   */
-  const parseHTML = (html) => {
-    const tempDiv = document.createElement('div');
-    tempDiv.innerHTML = html;
-    return document.createDocumentFragment().appendChild(tempDiv);
-  };
-
   /**
    * Private message inbox block functionality.
    */
@@ -72,7 +61,7 @@
      * @param {string} threads HTML content of threads.
      */
     insertPreviousThreads(threads) {
-      const contents = parseHTML(threads).childNodes;
+      const contents = Drupal.PrivateMessageUtils.parseHTML(threads).childNodes;
 
       contents.forEach((content) => {
         container.appendChild(content);
@@ -130,7 +119,7 @@
           if (existingThreads[threadId]) {
             existingThreads[threadId].remove();
           }
-          const newThreadContent = parseHTML(newThreads[threadId]).childNodes;
+          const newThreadContent = Drupal.PrivateMessageUtils.parseHTML(newThreads[threadId]).childNodes;
           newThreadContent.forEach((child) => container.appendChild(child));
         } else if (existingThreads[threadId]) {
           container.appendChild(existingThreads[threadId]);
diff --git a/js/private_message_utils.js b/js/private_message_utils.js
new file mode 100644
index 00000000..6599513e
--- /dev/null
+++ b/js/private_message_utils.js
@@ -0,0 +1,25 @@
+/**
+ * @file
+ * Shared utility functions for Private Message module.
+ */
+
+((Drupal) => {
+  'use strict';
+
+  Drupal.PrivateMessageUtils = {
+
+    /**
+     * Creates a temporary DOM container.
+     *
+     * @param {string} html The HTML string to parse.
+     * @returns {DocumentFragment} Parsed HTML content as a DocumentFragment.
+     */
+    parseHTML(html) {
+      const tempDiv = document.createElement('div');
+      tempDiv.innerHTML = html;
+      return document.createDocumentFragment().appendChild(tempDiv);
+    },
+
+  };
+
+})(Drupal);
diff --git a/private_message.libraries.yml b/private_message.libraries.yml
index ac605702..d5bac016 100644
--- a/private_message.libraries.yml
+++ b/private_message.libraries.yml
@@ -2,6 +2,21 @@ history_api:
   js:
     js/history_api.js: {}
 
+utils:
+  js:
+    js/private_message_utils.js: {}
+  dependencies:
+    - core/drupal
+
+slide:
+  js:
+    js/private_message_slide.js: { }
+  css:
+    theme:
+      css/private_message_slide.css: { }
+  dependencies:
+    - core/drupal
+
 inbox_block_script:
   js:
     js/private_message_inbox_previous.js: {}
@@ -11,7 +26,8 @@ inbox_block_script:
     - core/once
     - core/drupal.ajax
     - core/drupalSettings
-    - private_message/private_message_slide
+    - private_message/slide
+    - private_message/utils
 
 inbox_block_style:
   css:
@@ -52,12 +68,3 @@ message_form:
     - core/once
     - core/drupalSettings
     - private_message/browser_notification
-
-private_message_slide:
-  js:
-    js/private_message_slide.js: { }
-  css:
-    theme:
-      css/private_message_slide.css: { }
-  dependencies:
-    - core/drupal
-- 
GitLab


From f4d036a5226aa8cd96d1075444d96bee11a28869 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Wed, 4 Dec 2024 10:40:36 +0100
Subject: [PATCH 07/23] Move as utils function.

---
 js/private_message_inbox_block.js | 21 +--------------------
 js/private_message_utils.js       | 18 ++++++++++++++++++
 2 files changed, 19 insertions(+), 20 deletions(-)

diff --git a/js/private_message_inbox_block.js b/js/private_message_inbox_block.js
index c29989bc..c42a5682 100644
--- a/js/private_message_inbox_block.js
+++ b/js/private_message_inbox_block.js
@@ -129,25 +129,6 @@
       // TODO: it generates js errors
       // Drupal.attachBehaviors(container);
     },
-
-    /**
-     * Sets the active thread visually.
-     * @param {string} threadId The thread ID.
-     */
-    setActiveThread(threadId) {
-      const activeThread = container.querySelector('.active-thread');
-      if (activeThread) {
-        activeThread.classList.remove('active-thread');
-      }
-
-      const targetThread = container.querySelector(
-        `.private-message-thread[data-thread-id="${threadId}"]`,
-      );
-      if (targetThread) {
-        targetThread.classList.remove('unread-thread');
-        targetThread.classList.add('active-thread');
-      }
-    },
   };
 
   /**
@@ -168,7 +149,7 @@
 
       const threadId = container.querySelector('.private-message-thread')?.dataset.threadId;
       if (threadId) {
-        Drupal.PrivateMessageInboxBlock.setActiveThread(threadId);
+        Drupal.PrivateMessageUtils.setActiveThread(threadId);
       }
 
       if (
diff --git a/js/private_message_utils.js b/js/private_message_utils.js
index 6599513e..f2d23810 100644
--- a/js/private_message_utils.js
+++ b/js/private_message_utils.js
@@ -20,6 +20,24 @@
       return document.createDocumentFragment().appendChild(tempDiv);
     },
 
+    /**
+     * Sets the active thread visually.
+     * @param {string} threadId The thread ID.
+     */
+    setActiveThread(threadId) {
+      const activeThread = document.querySelector('.private-message-thread--full-container .active-thread');
+      if (activeThread) {
+        activeThread.classList.remove('active-thread');
+      }
+
+      const targetThread = document.querySelector(
+        `.private-message-thread--full-container .private-message-thread[data-thread-id="${threadId}"]`,
+      );
+      if (targetThread) {
+        targetThread.classList.remove('unread-thread');
+        targetThread.classList.add('active-thread');
+      }
+    },
   };
 
 })(Drupal);
-- 
GitLab


From c5b1cf5c936326be54cc2ace218359cb8d5cd8f2 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Wed, 4 Dec 2024 11:07:02 +0100
Subject: [PATCH 08/23] Add click listener.

---
 js/private_message_inbox_block.js | 23 ++++++++++-------------
 js/private_message_thread.js      | 29 +++++++++++++++++++++++++++++
 private_message.libraries.yml     |  1 +
 3 files changed, 40 insertions(+), 13 deletions(-)

diff --git a/js/private_message_inbox_block.js b/js/private_message_inbox_block.js
index c42a5682..72640bd0 100644
--- a/js/private_message_inbox_block.js
+++ b/js/private_message_inbox_block.js
@@ -64,11 +64,12 @@
       const contents = Drupal.PrivateMessageUtils.parseHTML(threads).childNodes;
 
       contents.forEach((content) => {
-        container.appendChild(content);
+        let newElement = container.appendChild(content);
+
+        Drupal.attachBehaviors(newElement);
       });
 
       // contents.css('display', 'none').appendTo(container).slideDown(300);
-      // Drupal.attachBehaviors(contents[0]);
     },
 
     /**
@@ -120,14 +121,16 @@
             existingThreads[threadId].remove();
           }
           const newThreadContent = Drupal.PrivateMessageUtils.parseHTML(newThreads[threadId]).childNodes;
-          newThreadContent.forEach((child) => container.appendChild(child));
+          newThreadContent.forEach((child) => {
+              let newElement = container.appendChild(child);
+              Drupal.attachBehaviors(newElement);
+            },
+          );
         } else if (existingThreads[threadId]) {
-          container.appendChild(existingThreads[threadId]);
+          const newElement = container.appendChild(existingThreads[threadId]);
+          Drupal.attachBehaviors(newElement);
         }
       });
-
-      // TODO: it generates js errors
-      // Drupal.attachBehaviors(container);
     },
   };
 
@@ -169,12 +172,6 @@
         context,
         Drupal.PrivateMessageInboxBlock.triggerLoadOldThreadCallback,
       );
-
-      // Unbind the 'click' event from '.private-message-inbox-thread-link' elements
-      // const inboxThreadLinks = context.querySelectorAll('.private-message-inbox-thread-link');
-      // inboxThreadLinks.forEach((link) => {
-      //   link.removeEventListener('click', inboxThreadLinkListenerHandler);
-      // });
     },
   };
 
diff --git a/js/private_message_thread.js b/js/private_message_thread.js
index 288af094..43ec3352 100644
--- a/js/private_message_thread.js
+++ b/js/private_message_thread.js
@@ -338,8 +338,31 @@ Drupal.PrivateMessages.threadChange = {};
     }
   }
 
+  /**
+   * Click Handler executed when private message threads are clicked.
+   *
+   * Loads the thread into the private message window.
+   * @param {Event} e The event.
+   */
+  function inboxThreadLinkListenerHandler (e) {
+    e.preventDefault();
+    const threadId = e.currentTarget.dataset.threadId;
+    if (threadId) {
+      Drupal.PrivateMessages.loadThread(threadId);
+      Drupal.PrivateMessageUtils.setActiveThread(threadId);
+    }
+  }
+
   Drupal.behaviors.privateMessageThread = {
     attach(context) {
+      once(
+        'inbox-thread-link-listener',
+        '.private-message-inbox-thread-link',
+        context,
+      ).forEach((el) => {
+        el.addEventListener('click', inboxThreadLinkListenerHandler);
+      });
+
       init();
       loadPreviousListener(context);
       currentThreadId = threadWrapper
@@ -414,6 +437,12 @@ Drupal.PrivateMessages.threadChange = {};
       $(context)
         .find('#load-previous-messages')
         .unbind('click', loadPreviousListenerHandler);
+
+      // Unbind the 'click' event from '.private-message-inbox-thread-link' elements
+      const inboxThreadLinks = context.querySelectorAll('.private-message-inbox-thread-link');
+      inboxThreadLinks.forEach((link) => {
+        link.removeEventListener('click', inboxThreadLinkListenerHandler);
+      });
     },
   };
 
diff --git a/private_message.libraries.yml b/private_message.libraries.yml
index d5bac016..97d49e6e 100644
--- a/private_message.libraries.yml
+++ b/private_message.libraries.yml
@@ -55,6 +55,7 @@ private_message_thread_script:
     - core/drupalSettings
     - core/once
     - private_message/history_api
+    - private_message/utils
 
 private_message_thread_style:
   css:
-- 
GitLab


From eb962ba34831a3d7dfa1fefb7b2a1a9a3d8c6259 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Wed, 4 Dec 2024 11:11:34 +0100
Subject: [PATCH 09/23] Impove standards.

---
 js/private_message_inbox_block.js    | 32 +++++++++++++++++-----------
 js/private_message_inbox_previous.js | 18 +++++-----------
 js/private_message_thread.js         |  8 ++++---
 js/private_message_utils.js          | 10 ++++-----
 4 files changed, 33 insertions(+), 35 deletions(-)

diff --git a/js/private_message_inbox_block.js b/js/private_message_inbox_block.js
index 72640bd0..3a4f79a4 100644
--- a/js/private_message_inbox_block.js
+++ b/js/private_message_inbox_block.js
@@ -12,7 +12,6 @@
    * Private message inbox block functionality.
    */
   Drupal.PrivateMessageInboxBlock = {
-
     /**
      * Updates the inbox with new threads.
      */
@@ -45,7 +44,7 @@
      * Sets timeout for update inbox callback.
      */
     setTimeoutForUpdateInboxCallback() {
-      let updateInterval =
+      const updateInterval =
         drupalSettings.privateMessageInboxBlock.ajaxRefreshRate * 1000;
       if (!updateInterval) {
         return;
@@ -64,7 +63,7 @@
       const contents = Drupal.PrivateMessageUtils.parseHTML(threads).childNodes;
 
       contents.forEach((content) => {
-        let newElement = container.appendChild(content);
+        const newElement = container.appendChild(content);
 
         Drupal.attachBehaviors(newElement);
       });
@@ -86,7 +85,10 @@
 
       let oldestTimestamp = Infinity;
       container.querySelectorAll('.private-message-thread').forEach((el) => {
-        oldestTimestamp = Math.min(oldestTimestamp, parseInt(el.dataset.lastUpdate, 10));
+        oldestTimestamp = Math.min(
+          oldestTimestamp,
+          parseInt(el.dataset.lastUpdate, 10),
+        );
       });
 
       Drupal.ajax({
@@ -111,21 +113,24 @@
     reorderInbox(threadIds, newThreads) {
       const existingThreads = {};
 
-      container.querySelectorAll(':scope > .private-message-thread-inbox').forEach((el) => {
-        existingThreads[el.dataset.threadId] = el;
-      });
+      container
+        .querySelectorAll(':scope > .private-message-thread-inbox')
+        .forEach((el) => {
+          existingThreads[el.dataset.threadId] = el;
+        });
 
       threadIds.forEach((threadId) => {
         if (newThreads[threadId]) {
           if (existingThreads[threadId]) {
             existingThreads[threadId].remove();
           }
-          const newThreadContent = Drupal.PrivateMessageUtils.parseHTML(newThreads[threadId]).childNodes;
+          const newThreadContent = Drupal.PrivateMessageUtils.parseHTML(
+            newThreads[threadId],
+          ).childNodes;
           newThreadContent.forEach((child) => {
-              let newElement = container.appendChild(child);
-              Drupal.attachBehaviors(newElement);
-            },
-          );
+            const newElement = container.appendChild(child);
+            Drupal.attachBehaviors(newElement);
+          });
         } else if (existingThreads[threadId]) {
           const newElement = container.appendChild(existingThreads[threadId]);
           Drupal.attachBehaviors(newElement);
@@ -150,7 +155,8 @@
       }
       container = containerFormContext;
 
-      const threadId = container.querySelector('.private-message-thread')?.dataset.threadId;
+      const threadId = container.querySelector('.private-message-thread')
+        ?.dataset.threadId;
       if (threadId) {
         Drupal.PrivateMessageUtils.setActiveThread(threadId);
       }
diff --git a/js/private_message_inbox_previous.js b/js/private_message_inbox_previous.js
index e4cc2d80..b13b7040 100644
--- a/js/private_message_inbox_previous.js
+++ b/js/private_message_inbox_previous.js
@@ -4,7 +4,6 @@
  */
 
 ((Drupal) => {
-
   /**
    * Previous functionality.
    */
@@ -23,8 +22,8 @@
         'afterend',
         `<div id="${this.wrapperId}">
           <a href="#" id="${this.buttonId}" aria-label="${Drupal.t(
-          'Load previous threads',
-        )}">${Drupal.t('Load Previous')}</a>
+            'Load previous threads',
+          )}">${Drupal.t('Load Previous')}</a>
         </div>`,
       );
 
@@ -36,9 +35,7 @@
      * Slides done the button.
      */
     slideDownButton() {
-      const buttonWrapper = document.getElementById(
-        this.wrapperId,
-      );
+      const buttonWrapper = document.getElementById(this.wrapperId);
       if (buttonWrapper) {
         Drupal.PrivateMessageSlide.toggleSlide(buttonWrapper, false);
       }
@@ -51,15 +48,10 @@
      * @param {Function} callback - The function to detach from the click event.
      */
     detachEventListener(context, callback) {
-      const button = context.querySelector('#' + this.buttonId);
+      const button = context.querySelector(`#${this.buttonId}`);
       if (button) {
-        button.removeEventListener(
-          'click',
-          callback,
-        );
+        button.removeEventListener('click', callback);
       }
     },
   };
-
 })(Drupal);
-
diff --git a/js/private_message_thread.js b/js/private_message_thread.js
index 43ec3352..95922af6 100644
--- a/js/private_message_thread.js
+++ b/js/private_message_thread.js
@@ -344,9 +344,9 @@ Drupal.PrivateMessages.threadChange = {};
    * Loads the thread into the private message window.
    * @param {Event} e The event.
    */
-  function inboxThreadLinkListenerHandler (e) {
+  function inboxThreadLinkListenerHandler(e) {
     e.preventDefault();
-    const threadId = e.currentTarget.dataset.threadId;
+    const { threadId } = e.currentTarget.dataset;
     if (threadId) {
       Drupal.PrivateMessages.loadThread(threadId);
       Drupal.PrivateMessageUtils.setActiveThread(threadId);
@@ -439,7 +439,9 @@ Drupal.PrivateMessages.threadChange = {};
         .unbind('click', loadPreviousListenerHandler);
 
       // Unbind the 'click' event from '.private-message-inbox-thread-link' elements
-      const inboxThreadLinks = context.querySelectorAll('.private-message-inbox-thread-link');
+      const inboxThreadLinks = context.querySelectorAll(
+        '.private-message-inbox-thread-link',
+      );
       inboxThreadLinks.forEach((link) => {
         link.removeEventListener('click', inboxThreadLinkListenerHandler);
       });
diff --git a/js/private_message_utils.js b/js/private_message_utils.js
index f2d23810..54c3ab1d 100644
--- a/js/private_message_utils.js
+++ b/js/private_message_utils.js
@@ -4,15 +4,12 @@
  */
 
 ((Drupal) => {
-  'use strict';
-
   Drupal.PrivateMessageUtils = {
-
     /**
      * Creates a temporary DOM container.
      *
      * @param {string} html The HTML string to parse.
-     * @returns {DocumentFragment} Parsed HTML content as a DocumentFragment.
+     * @return {DocumentFragment} Parsed HTML content as a DocumentFragment.
      */
     parseHTML(html) {
       const tempDiv = document.createElement('div');
@@ -25,7 +22,9 @@
      * @param {string} threadId The thread ID.
      */
     setActiveThread(threadId) {
-      const activeThread = document.querySelector('.private-message-thread--full-container .active-thread');
+      const activeThread = document.querySelector(
+        '.private-message-thread--full-container .active-thread',
+      );
       if (activeThread) {
         activeThread.classList.remove('active-thread');
       }
@@ -39,5 +38,4 @@
       }
     },
   };
-
 })(Drupal);
-- 
GitLab


From 0036635ea3fd3d85ac6617c323f1e30c7be2d101 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Wed, 4 Dec 2024 12:01:58 +0100
Subject: [PATCH 10/23] Add init method

---
 js/private_message_inbox_block.js | 112 +++++++++++++++++-------------
 1 file changed, 63 insertions(+), 49 deletions(-)

diff --git a/js/private_message_inbox_block.js b/js/private_message_inbox_block.js
index 3a4f79a4..f47ad748 100644
--- a/js/private_message_inbox_block.js
+++ b/js/private_message_inbox_block.js
@@ -7,15 +7,32 @@
   let container;
   let loadingPrevInProgress = false;
   let loadingNewInProgress = false;
+  let updateTimeoutId = null;
 
   /**
    * Private message inbox block functionality.
    */
   Drupal.PrivateMessageInboxBlock = {
+    /**
+     * Initialize the block with default state and handlers.
+     *
+     * @param {HTMLElement} blockWrapper - The inbox block.
+     */
+    init(blockWrapper) {
+      container = blockWrapper;
+      const threadId = container.querySelector('.private-message-thread')
+        ?.dataset.threadId;
+      if (threadId) {
+        Drupal.PrivateMessageUtils.setActiveThread(threadId);
+      }
+      this.attachLoadOldButton();
+      this.scheduleInboxUpdate();
+    },
+
     /**
      * Updates the inbox with new threads.
      */
-    triggerUpdateInboxCallback() {
+    updateInbox() {
       if (loadingNewInProgress) {
         return;
       }
@@ -36,36 +53,36 @@
         .execute()
         .always(() => {
           loadingNewInProgress = false;
-          Drupal.PrivateMessageInboxBlock.setTimeoutForUpdateInboxCallback();
+          this.scheduleInboxUpdate();
         });
     },
 
     /**
-     * Sets timeout for update inbox callback.
+     * Sets a timeout for inbox updates.
      */
-    setTimeoutForUpdateInboxCallback() {
-      const updateInterval =
+    scheduleInboxUpdate() {
+      if (updateTimeoutId) {
+        window.clearTimeout(updateTimeoutId);
+      }
+      const interval =
         drupalSettings.privateMessageInboxBlock.ajaxRefreshRate * 1000;
-      if (!updateInterval) {
-        return;
+      if (interval) {
+        updateTimeoutId = window.setTimeout(() => this.updateInbox(), interval);
       }
-      window.setTimeout(
-        Drupal.PrivateMessageInboxBlock.triggerUpdateInboxCallback,
-        updateInterval,
-      );
     },
 
     /**
      * Appends older threads to the inbox.
-     * @param {string} threads HTML content of threads.
+     * @param {string} threadsHtml HTML content of threads.
      */
-    insertPreviousThreads(threads) {
-      const contents = Drupal.PrivateMessageUtils.parseHTML(threads).childNodes;
+    insertPreviousThreads(threadsHtml) {
+      const newNodes =
+        Drupal.PrivateMessageUtils.parseHTML(threadsHtml).childNodes;
 
-      contents.forEach((content) => {
-        const newElement = container.appendChild(content);
+      newNodes.forEach((node) => {
+        const appendedElement = container.appendChild(node);
 
-        Drupal.attachBehaviors(newElement);
+        Drupal.attachBehaviors(appendedElement);
       });
 
       // contents.css('display', 'none').appendTo(container).slideDown(300);
@@ -75,7 +92,7 @@
      * Handles loading older threads.
      * @param {Event} e The click event.
      */
-    triggerLoadOldThreadCallback(e) {
+    loadOldThreads(e) {
       e.preventDefault();
       if (loadingPrevInProgress) {
         return;
@@ -83,13 +100,11 @@
 
       loadingPrevInProgress = true;
 
-      let oldestTimestamp = Infinity;
-      container.querySelectorAll('.private-message-thread').forEach((el) => {
-        oldestTimestamp = Math.min(
-          oldestTimestamp,
-          parseInt(el.dataset.lastUpdate, 10),
-        );
-      });
+      const oldestTimestamp = Array.from(
+        container.querySelectorAll('.private-message-thread'),
+      ).reduce((minTime, el) => {
+        return Math.min(minTime, parseInt(el.dataset.lastUpdate, 10));
+      }, Infinity);
 
       Drupal.ajax({
         url: drupalSettings.privateMessageInboxBlock.loadPrevUrl,
@@ -128,15 +143,31 @@
             newThreads[threadId],
           ).childNodes;
           newThreadContent.forEach((child) => {
-            const newElement = container.appendChild(child);
-            Drupal.attachBehaviors(newElement);
+            const appendedElement = container.appendChild(child);
+            Drupal.attachBehaviors(appendedElement);
           });
         } else if (existingThreads[threadId]) {
-          const newElement = container.appendChild(existingThreads[threadId]);
-          Drupal.attachBehaviors(newElement);
+          const appendedElement = container.appendChild(
+            existingThreads[threadId],
+          );
+          Drupal.attachBehaviors(appendedElement);
         }
       });
     },
+
+    /**
+     * Attaches the "Load Older Threads" button handler.
+     */
+    attachLoadOldButton() {
+      if (
+        drupalSettings.privateMessageInboxBlock.totalThreads >
+        drupalSettings.privateMessageInboxBlock.itemsToShow
+      ) {
+        Drupal.privateMessageInboxPrevious.displayButton(container, (e) =>
+          this.loadOldThreads(e),
+        );
+      }
+    },
   };
 
   /**
@@ -153,30 +184,13 @@
       if (!containerFormContext) {
         return;
       }
-      container = containerFormContext;
-
-      const threadId = container.querySelector('.private-message-thread')
-        ?.dataset.threadId;
-      if (threadId) {
-        Drupal.PrivateMessageUtils.setActiveThread(threadId);
-      }
-
-      if (
-        drupalSettings.privateMessageInboxBlock.totalThreads >
-        drupalSettings.privateMessageInboxBlock.itemsToShow
-      ) {
-        Drupal.privateMessageInboxPrevious.displayButton(
-          container,
-          Drupal.PrivateMessageInboxBlock.triggerLoadOldThreadCallback,
-        );
-      }
 
-      Drupal.PrivateMessageInboxBlock.setTimeoutForUpdateInboxCallback();
+      Drupal.PrivateMessageInboxBlock.init(containerFormContext);
     },
     detach(context) {
       Drupal.privateMessageInboxPrevious.detachEventListener(
         context,
-        Drupal.PrivateMessageInboxBlock.triggerLoadOldThreadCallback,
+        Drupal.PrivateMessageInboxBlock.loadOldThreads,
       );
     },
   };
@@ -214,6 +228,6 @@
    * Custom AJAX command to trigger an inbox update.
    */
   Drupal.AjaxCommands.prototype.privateMessageTriggerInboxUpdate = () => {
-    Drupal.PrivateMessageInboxBlock.triggerUpdateInboxCallback();
+    Drupal.PrivateMessageInboxBlock.updateInbox();
   };
 })(Drupal, drupalSettings, window, once);
-- 
GitLab


From e5056d75936df724abfe2ea9634b7f42f376bf83 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Wed, 4 Dec 2024 12:26:48 +0100
Subject: [PATCH 11/23] Update slide.

---
 js/private_message_inbox_block.js |  3 +--
 js/private_message_slide.js       | 24 ++++++++----------------
 2 files changed, 9 insertions(+), 18 deletions(-)

diff --git a/js/private_message_inbox_block.js b/js/private_message_inbox_block.js
index f47ad748..df934c78 100644
--- a/js/private_message_inbox_block.js
+++ b/js/private_message_inbox_block.js
@@ -83,9 +83,8 @@
         const appendedElement = container.appendChild(node);
 
         Drupal.attachBehaviors(appendedElement);
+        Drupal.PrivateMessageSlide.toggleSlide(appendedElement, true);
       });
-
-      // contents.css('display', 'none').appendTo(container).slideDown(300);
     },
 
     /**
diff --git a/js/private_message_slide.js b/js/private_message_slide.js
index 3de49d42..f6f86cc0 100644
--- a/js/private_message_slide.js
+++ b/js/private_message_slide.js
@@ -23,24 +23,16 @@
     element.classList.add('private-message-slide');
 
     if (expand) {
-      // Prepare to slide down
-      element.classList.remove('private-message-slide__collapsed');
-      element.classList.add('private-message-slide__expanded');
+      const height = `${element.scrollHeight}px`;
+      element.style.height = '0';
 
-      // Dynamically set the height to the element's natural height
-      element.style.height = `${element.scrollHeight}px`;
-
-      // Reset height after transition to prevent issues on resize
-      element.addEventListener(
-        'transitionend',
-        () => {
-          element.style.height = ''; // Clear inline height to use natural height
-        },
-        { once: true },
-      );
+      requestAnimationFrame(() => {
+        element.classList.remove('private-message-slide__collapsed');
+        element.classList.add('private-message-slide__expanded');
+        element.style.height = height;
+      });
     } else {
-      // Slide up
-      element.style.height = `${element.scrollHeight}px`; // Set the current height explicitly
+      element.style.height = `${element.scrollHeight}px`;
       requestAnimationFrame(() => {
         element.classList.remove('private-message-slide__expanded');
         element.classList.add('private-message-slide__collapsed');
-- 
GitLab


From f734a4f8f3f702e4accabd6d08ca5567d94bf46e Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Wed, 4 Dec 2024 13:45:48 +0100
Subject: [PATCH 12/23] Update slide.

---
 js/private_message_slide.js | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/js/private_message_slide.js b/js/private_message_slide.js
index f6f86cc0..96ade947 100644
--- a/js/private_message_slide.js
+++ b/js/private_message_slide.js
@@ -23,20 +23,20 @@
     element.classList.add('private-message-slide');
 
     if (expand) {
-      const height = `${element.scrollHeight}px`;
       element.style.height = '0';
+      element.classList.remove('private-message-slide__collapsed');
 
       requestAnimationFrame(() => {
-        element.classList.remove('private-message-slide__collapsed');
         element.classList.add('private-message-slide__expanded');
-        element.style.height = height;
+        element.style.height = `${element.scrollHeight}px`;
       });
     } else {
       element.style.height = `${element.scrollHeight}px`;
+      element.classList.remove('private-message-slide__expanded');
+
       requestAnimationFrame(() => {
-        element.classList.remove('private-message-slide__expanded');
         element.classList.add('private-message-slide__collapsed');
-        element.style.height = '0'; // Collapse the height
+        element.style.height = '0';
       });
     }
   };
-- 
GitLab


From 391cfd1858095b5c132be2be215f920bfc2d2b80 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Wed, 4 Dec 2024 14:39:43 +0100
Subject: [PATCH 13/23] Move variables to class

---
 js/private_message_inbox_block.js    | 110 ++++++++++++++++-----------
 js/private_message_inbox_previous.js |  12 ++-
 js/private_message_slide.js          |   6 +-
 js/private_message_utils.js          |  10 ++-
 4 files changed, 86 insertions(+), 52 deletions(-)

diff --git a/js/private_message_inbox_block.js b/js/private_message_inbox_block.js
index df934c78..af55616a 100644
--- a/js/private_message_inbox_block.js
+++ b/js/private_message_inbox_block.js
@@ -4,43 +4,46 @@
  */
 
 ((Drupal, drupalSettings, window, once) => {
-  let container;
-  let loadingPrevInProgress = false;
-  let loadingNewInProgress = false;
-  let updateTimeoutId = null;
-
   /**
    * Private message inbox block functionality.
    */
-  Drupal.PrivateMessageInboxBlock = {
+  class PrivateMessageInboxBlock {
+    constructor() {
+      this.container = null;
+      this.loadingPrevInProgress = false;
+      this.loadingNewInProgress = false;
+      this.updateTimeoutId = null;
+    }
+
     /**
      * Initialize the block with default state and handlers.
      *
-     * @param {HTMLElement} blockWrapper - The inbox block.
+     * @param {HTMLElement} blockWrapper
+     *   The inbox block.
      */
     init(blockWrapper) {
-      container = blockWrapper;
-      const threadId = container.querySelector('.private-message-thread')
+      this.container = blockWrapper;
+      const threadId = this.container.querySelector('.private-message-thread')
         ?.dataset.threadId;
       if (threadId) {
         Drupal.PrivateMessageUtils.setActiveThread(threadId);
       }
       this.attachLoadOldButton();
       this.scheduleInboxUpdate();
-    },
+    }
 
     /**
      * Updates the inbox with new threads.
      */
     updateInbox() {
-      if (loadingNewInProgress) {
+      if (this.loadingNewInProgress) {
         return;
       }
 
-      loadingNewInProgress = true;
+      this.loadingNewInProgress = true;
       const ids = {};
 
-      container
+      this.container
         .querySelectorAll('.private-message-thread-inbox')
         .forEach((el) => {
           ids[el.dataset.threadId] = el.dataset.lastUpdate;
@@ -52,55 +55,62 @@
       })
         .execute()
         .always(() => {
-          loadingNewInProgress = false;
+          this.loadingNewInProgress = false;
           this.scheduleInboxUpdate();
         });
-    },
+    }
 
     /**
      * Sets a timeout for inbox updates.
      */
     scheduleInboxUpdate() {
-      if (updateTimeoutId) {
-        window.clearTimeout(updateTimeoutId);
+      if (this.updateTimeoutId) {
+        window.clearTimeout(this.updateTimeoutId);
       }
       const interval =
         drupalSettings.privateMessageInboxBlock.ajaxRefreshRate * 1000;
       if (interval) {
-        updateTimeoutId = window.setTimeout(() => this.updateInbox(), interval);
+        this.updateTimeoutId = window.setTimeout(
+          () => this.updateInbox(),
+          interval,
+        );
       }
-    },
+    }
 
     /**
      * Appends older threads to the inbox.
-     * @param {string} threadsHtml HTML content of threads.
+     *
+     * @param {string} threadsHtml
+     *   HTML content of threads.
      */
     insertPreviousThreads(threadsHtml) {
       const newNodes =
         Drupal.PrivateMessageUtils.parseHTML(threadsHtml).childNodes;
 
       newNodes.forEach((node) => {
-        const appendedElement = container.appendChild(node);
+        const appendedElement = this.container.appendChild(node);
 
         Drupal.attachBehaviors(appendedElement);
         Drupal.PrivateMessageSlide.toggleSlide(appendedElement, true);
       });
-    },
+    }
 
     /**
      * Handles loading older threads.
-     * @param {Event} e The click event.
+     *
+     * @param {Event} e
+     *   The click event.
      */
     loadOldThreads(e) {
       e.preventDefault();
-      if (loadingPrevInProgress) {
+      if (this.loadingPrevInProgress) {
         return;
       }
 
-      loadingPrevInProgress = true;
+      this.loadingPrevInProgress = true;
 
       const oldestTimestamp = Array.from(
-        container.querySelectorAll('.private-message-thread'),
+        this.container.querySelectorAll('.private-message-thread'),
       ).reduce((minTime, el) => {
         return Math.min(minTime, parseInt(el.dataset.lastUpdate, 10));
       }, Infinity);
@@ -114,20 +124,22 @@
       })
         .execute()
         .always(() => {
-          loadingPrevInProgress = false;
+          this.loadingPrevInProgress = false;
         });
-    },
+    }
 
     /**
      * Reorders the inbox to show the newest threads first.
      *
-     * @param {Array} threadIds Thread IDs in the desired order.
-     * @param {Array} newThreads HTML content of new threads keyed by thread ID.
+     * @param {Array} threadIds
+     *   Thread IDs in the desired order.
+     * @param {Array} newThreads
+     *   HTML content of new threads keyed by thread ID.
      */
     reorderInbox(threadIds, newThreads) {
       const existingThreads = {};
 
-      container
+      this.container
         .querySelectorAll(':scope > .private-message-thread-inbox')
         .forEach((el) => {
           existingThreads[el.dataset.threadId] = el;
@@ -142,17 +154,17 @@
             newThreads[threadId],
           ).childNodes;
           newThreadContent.forEach((child) => {
-            const appendedElement = container.appendChild(child);
+            const appendedElement = this.container.appendChild(child);
             Drupal.attachBehaviors(appendedElement);
           });
         } else if (existingThreads[threadId]) {
-          const appendedElement = container.appendChild(
+          const appendedElement = this.container.appendChild(
             existingThreads[threadId],
           );
           Drupal.attachBehaviors(appendedElement);
         }
       });
-    },
+    }
 
     /**
      * Attaches the "Load Older Threads" button handler.
@@ -162,12 +174,14 @@
         drupalSettings.privateMessageInboxBlock.totalThreads >
         drupalSettings.privateMessageInboxBlock.itemsToShow
       ) {
-        Drupal.privateMessageInboxPrevious.displayButton(container, (e) =>
+        Drupal.privateMessageInboxPrevious.displayButton(this.container, (e) =>
           this.loadOldThreads(e),
         );
       }
-    },
-  };
+    }
+  }
+
+  const privateMessageInboxBlock = new PrivateMessageInboxBlock();
 
   /**
    * Attaches the private message inbox block behavior.
@@ -184,25 +198,30 @@
         return;
       }
 
-      Drupal.PrivateMessageInboxBlock.init(containerFormContext);
+      privateMessageInboxBlock.init(containerFormContext);
     },
     detach(context) {
       Drupal.privateMessageInboxPrevious.detachEventListener(
         context,
-        Drupal.PrivateMessageInboxBlock.loadOldThreads,
+        privateMessageInboxBlock.loadOldThreads.bind(privateMessageInboxBlock),
       );
     },
   };
 
   /**
-   * Custom AJAX command to insert older threads into the inbox.
+   * Custom AJAX commands for private message inbox.
+   *
+   * @param {Drupal.Ajax} ajax
+   *   The Drupal Ajax object.
+   * @param {object} response
+   *   Object holding the server response.
    */
   Drupal.AjaxCommands.prototype.insertInboxOldPrivateMessageThreads = (
     ajax,
     response,
   ) => {
     if (response.threads) {
-      Drupal.PrivateMessageInboxBlock.insertPreviousThreads(response.threads);
+      privateMessageInboxBlock.insertPreviousThreads(response.threads);
     }
 
     if (!response.threads || !response.hasNext) {
@@ -212,12 +231,17 @@
 
   /**
    * Custom AJAX command to update the inbox with new threads.
+   *
+   * @param {Drupal.Ajax} ajax
+   *   The Drupal Ajax object.
+   * @param {object} response
+   *   Object holding the server response.
    */
   Drupal.AjaxCommands.prototype.privateMessageInboxUpdate = (
     ajax,
     response,
   ) => {
-    Drupal.PrivateMessageInboxBlock.reorderInbox(
+    privateMessageInboxBlock.reorderInbox(
       response.threadIds,
       response.newThreads,
     );
@@ -227,6 +251,6 @@
    * Custom AJAX command to trigger an inbox update.
    */
   Drupal.AjaxCommands.prototype.privateMessageTriggerInboxUpdate = () => {
-    Drupal.PrivateMessageInboxBlock.updateInbox();
+    privateMessageInboxBlock.updateInbox();
   };
 })(Drupal, drupalSettings, window, once);
diff --git a/js/private_message_inbox_previous.js b/js/private_message_inbox_previous.js
index b13b7040..d470b785 100644
--- a/js/private_message_inbox_previous.js
+++ b/js/private_message_inbox_previous.js
@@ -14,8 +14,10 @@
     /**
      * Display the previous button.
      *
-     * @param {HTMLElement} blockElement - The element where the button will be added.
-     * @param {Function} callback - The function to call on button click.
+     * @param {HTMLElement} blockElement
+     *   The element where the button will be added.
+     * @param {Function} callback
+     *   The function to call on button click.
      */
     displayButton(blockElement, callback) {
       blockElement.insertAdjacentHTML(
@@ -44,8 +46,10 @@
     /*
      * Detaches event listener.
      *
-     * @param {Document|HTMLElement} context - The context to search for the button.
-     * @param {Function} callback - The function to detach from the click event.
+     * @param {Document|HTMLElement} context
+     *   The context to search for the button.
+     * @param {Function} callback
+     *   The function to detach from the click event.
      */
     detachEventListener(context, callback) {
       const button = context.querySelector(`#${this.buttonId}`);
diff --git a/js/private_message_slide.js b/js/private_message_slide.js
index 96ade947..dcd63076 100644
--- a/js/private_message_slide.js
+++ b/js/private_message_slide.js
@@ -10,8 +10,10 @@
   /**
    * Toggles slide-up and slide-down effects.
    *
-   * @param {HTMLElement} element - The element to toggle.
-   * @param {boolean} expand - True to slide down, false to slide up.
+   * @param {HTMLElement} element
+   *   The element to toggle.
+   * @param {boolean} expand
+   *   True to slide down, false to slide up.
    */
   Drupal.PrivateMessageSlide.toggleSlide = (element, expand) => {
     // Ensure the element is valid
diff --git a/js/private_message_utils.js b/js/private_message_utils.js
index 54c3ab1d..d8262270 100644
--- a/js/private_message_utils.js
+++ b/js/private_message_utils.js
@@ -8,8 +8,10 @@
     /**
      * Creates a temporary DOM container.
      *
-     * @param {string} html The HTML string to parse.
-     * @return {DocumentFragment} Parsed HTML content as a DocumentFragment.
+     * @param {string} html
+     *   The HTML string to parse.
+     * @return {DocumentFragment}
+     *   Parsed HTML content as a DocumentFragment.
      */
     parseHTML(html) {
       const tempDiv = document.createElement('div');
@@ -19,7 +21,9 @@
 
     /**
      * Sets the active thread visually.
-     * @param {string} threadId The thread ID.
+     *
+     * @param {string} threadId
+     *   The thread ID.
      */
     setActiveThread(threadId) {
       const activeThread = document.querySelector(
-- 
GitLab


From 98b0723c05646d6cdb28c17ae04336256f81e194 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Wed, 4 Dec 2024 15:07:59 +0100
Subject: [PATCH 14/23] Drop jQuery

---
 private_message.libraries.yml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/private_message.libraries.yml b/private_message.libraries.yml
index 97d49e6e..77a12bff 100644
--- a/private_message.libraries.yml
+++ b/private_message.libraries.yml
@@ -22,7 +22,6 @@ inbox_block_script:
     js/private_message_inbox_previous.js: {}
     js/private_message_inbox_block.js: {}
   dependencies:
-    - core/jquery
     - core/once
     - core/drupal.ajax
     - core/drupalSettings
-- 
GitLab


From fa6afe03d61706487fb58af464e6ad61d0662127 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Wed, 4 Dec 2024 16:05:14 +0100
Subject: [PATCH 15/23] Limit potential duplicate calls.

---
 js/private_message_notification_block.js | 37 ++++++++++++++----------
 1 file changed, 22 insertions(+), 15 deletions(-)

diff --git a/js/private_message_notification_block.js b/js/private_message_notification_block.js
index 9cd4aeb9..2a0ce56e 100644
--- a/js/private_message_notification_block.js
+++ b/js/private_message_notification_block.js
@@ -4,8 +4,8 @@
  */
 
 ((Drupal, drupalSettings, window) => {
-  let refreshRate;
   let checkingCountInProgress = false;
+  let updateTimeoutId = null;
 
   /**
    * Private message notification block.
@@ -87,6 +87,24 @@
         pageTitle.textContent = pageTitle.textContent.replace(titlePattern, '');
       }
     },
+
+    /**
+     * Sets a timeout for count updates.
+     */
+    scheduleCountUpdate() {
+      if (updateTimeoutId) {
+        window.clearTimeout(updateTimeoutId);
+      }
+
+      const refreshRate =
+        drupalSettings.privateMessageNotificationBlock.ajaxRefreshRate * 1000;
+      if (refreshRate) {
+        updateTimeoutId = window.setTimeout(
+          Drupal.privateMessageNotificationBlock.triggerCountCallback,
+          refreshRate,
+        );
+      }
+    },
   };
 
   /**
@@ -102,14 +120,8 @@
         context,
       ).shift();
 
-      refreshRate =
-        drupalSettings.privateMessageNotificationBlock.ajaxRefreshRate * 1000;
-
-      if (notificationWrapper && refreshRate) {
-        window.setTimeout(
-          Drupal.privateMessageNotificationBlock.triggerCountCallback,
-          refreshRate,
-        );
+      if (notificationWrapper) {
+        Drupal.privateMessageNotificationBlock.scheduleCountUpdate();
       }
     },
   };
@@ -131,11 +143,6 @@
     );
 
     checkingCountInProgress = false;
-    if (refreshRate) {
-      window.setTimeout(
-        Drupal.privateMessageNotificationBlock.triggerCountCallback,
-        refreshRate,
-      );
-    }
+    Drupal.privateMessageNotificationBlock.scheduleCountUpdate();
   };
 })(Drupal, drupalSettings, window);
-- 
GitLab


From 8ce5db8942f06f0eb83497887c7de23452810735 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Thu, 5 Dec 2024 16:32:30 +0100
Subject: [PATCH 16/23] Add falling test.

---
 .../FunctionalJavascript/InboxBlockTest.php   | 20 +++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/tests/src/FunctionalJavascript/InboxBlockTest.php b/tests/src/FunctionalJavascript/InboxBlockTest.php
index 4cec4f21..65425a09 100644
--- a/tests/src/FunctionalJavascript/InboxBlockTest.php
+++ b/tests/src/FunctionalJavascript/InboxBlockTest.php
@@ -41,7 +41,7 @@ class InboxBlockTest extends WebDriverTestBase {
   public function setUp(): void {
     parent::setUp();
     $this->attachFullNameField();
-    $this->createTestingUsers(3);
+    $this->createTestingUsers(4);
 
     $this->threads[] = $this->createThreadWithMessages([
       $this->users['a'],
@@ -85,15 +85,27 @@ class InboxBlockTest extends WebDriverTestBase {
    * Tests load previous functionality.
    */
   public function testLoadPrevious(): void {
+    // 3 more threads, together we have 5
+    $this->threads[] = $this->createThreadWithMessages([
+      $this->users['a'],
+      $this->users['d'],
+    ], $this->users['d']);
+
     $this->createThreadWithMessages([
       $this->users['a'],
       $this->users['b'],
       $this->users['c'],
     ], $this->users['c']);
 
+    $this->createThreadWithMessages([
+      $this->users['a'],
+      $this->users['b'],
+      $this->users['d'],
+    ], $this->users['d']);
+
     $settings = [
       'thread_count' => 1,
-      'ajax_load_count' => 1,
+      'ajax_load_count' => 2,
       'ajax_refresh_rate' => 100,
     ];
     $this->drupalPlaceBlock('private_message_inbox_block', $settings);
@@ -105,13 +117,13 @@ class InboxBlockTest extends WebDriverTestBase {
       ->getPage()
       ->clickLink('Load Previous');
     $this->assertSession()->assertWaitOnAjaxRequest();
-    $this->assertEquals(2, $this->countThreads());
+    $this->assertEquals(3, $this->countThreads());
 
     $this->getSession()
       ->getPage()
       ->clickLink('Load Previous');
     $this->assertSession()->assertWaitOnAjaxRequest();
-    $this->assertEquals(3, $this->countThreads());
+    $this->assertEquals(5, $this->countThreads());
     $this->assertSession()
       ->pageTextNotContains('Load Previous');
   }
-- 
GitLab


From 197a117a36013f302d9c258d4cabc4e98548cbe2 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Thu, 5 Dec 2024 17:19:21 +0100
Subject: [PATCH 17/23] Clone

---
 js/private_message_inbox_block.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/js/private_message_inbox_block.js b/js/private_message_inbox_block.js
index af55616a..61d2844f 100644
--- a/js/private_message_inbox_block.js
+++ b/js/private_message_inbox_block.js
@@ -88,7 +88,7 @@
         Drupal.PrivateMessageUtils.parseHTML(threadsHtml).childNodes;
 
       newNodes.forEach((node) => {
-        const appendedElement = this.container.appendChild(node);
+        const appendedElement = this.container.appendChild(node.cloneNode(true));
 
         Drupal.attachBehaviors(appendedElement);
         Drupal.PrivateMessageSlide.toggleSlide(appendedElement, true);
-- 
GitLab


From e62ab7c340b57ed15b9059e45c3b3239f1482d4d Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Thu, 5 Dec 2024 19:56:27 +0100
Subject: [PATCH 18/23] Eslint issues

---
 js/private_message_inbox_block.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/js/private_message_inbox_block.js b/js/private_message_inbox_block.js
index 61d2844f..90366ef4 100644
--- a/js/private_message_inbox_block.js
+++ b/js/private_message_inbox_block.js
@@ -88,7 +88,9 @@
         Drupal.PrivateMessageUtils.parseHTML(threadsHtml).childNodes;
 
       newNodes.forEach((node) => {
-        const appendedElement = this.container.appendChild(node.cloneNode(true));
+        const appendedElement = this.container.appendChild(
+          node.cloneNode(true),
+        );
 
         Drupal.attachBehaviors(appendedElement);
         Drupal.PrivateMessageSlide.toggleSlide(appendedElement, true);
-- 
GitLab


From 263dd6cf82ac35d8bd4e3ac33bb762809e83d9e0 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Thu, 5 Dec 2024 20:23:10 +0100
Subject: [PATCH 19/23] Update parser.

---
 js/private_message_inbox_block.js | 10 ++++------
 js/private_message_utils.js       | 10 +++++++---
 2 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/js/private_message_inbox_block.js b/js/private_message_inbox_block.js
index 90366ef4..72132723 100644
--- a/js/private_message_inbox_block.js
+++ b/js/private_message_inbox_block.js
@@ -85,12 +85,10 @@
      */
     insertPreviousThreads(threadsHtml) {
       const newNodes =
-        Drupal.PrivateMessageUtils.parseHTML(threadsHtml).childNodes;
+        Drupal.PrivateMessageUtils.parseHTML(threadsHtml);
 
-      newNodes.forEach((node) => {
-        const appendedElement = this.container.appendChild(
-          node.cloneNode(true),
-        );
+      Array.from(newNodes).forEach((node) => {
+        const appendedElement = this.container.appendChild(node);
 
         Drupal.attachBehaviors(appendedElement);
         Drupal.PrivateMessageSlide.toggleSlide(appendedElement, true);
@@ -154,7 +152,7 @@
           }
           const newThreadContent = Drupal.PrivateMessageUtils.parseHTML(
             newThreads[threadId],
-          ).childNodes;
+          );
           newThreadContent.forEach((child) => {
             const appendedElement = this.container.appendChild(child);
             Drupal.attachBehaviors(appendedElement);
diff --git a/js/private_message_utils.js b/js/private_message_utils.js
index d8262270..4480cf5d 100644
--- a/js/private_message_utils.js
+++ b/js/private_message_utils.js
@@ -10,13 +10,17 @@
      *
      * @param {string} html
      *   The HTML string to parse.
-     * @return {DocumentFragment}
-     *   Parsed HTML content as a DocumentFragment.
+     * @returns {NodeListOf<ChildNode>}
+     *   Parsed HTML content as NodeList.
      */
     parseHTML(html) {
       const tempDiv = document.createElement('div');
       tempDiv.innerHTML = html;
-      return document.createDocumentFragment().appendChild(tempDiv);
+
+      return document
+        .createDocumentFragment()
+        .appendChild(tempDiv)
+        .childNodes;
     },
 
     /**
-- 
GitLab


From dd97ee4cede08ff503af256beb862757da8ed417 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Thu, 5 Dec 2024 20:24:38 +0100
Subject: [PATCH 20/23] Limit to 4

---
 tests/src/FunctionalJavascript/InboxBlockTest.php | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/tests/src/FunctionalJavascript/InboxBlockTest.php b/tests/src/FunctionalJavascript/InboxBlockTest.php
index 65425a09..bc405695 100644
--- a/tests/src/FunctionalJavascript/InboxBlockTest.php
+++ b/tests/src/FunctionalJavascript/InboxBlockTest.php
@@ -85,7 +85,7 @@ class InboxBlockTest extends WebDriverTestBase {
    * Tests load previous functionality.
    */
   public function testLoadPrevious(): void {
-    // 3 more threads, together we have 5
+    // 2 more threads, together we have 4.
     $this->threads[] = $this->createThreadWithMessages([
       $this->users['a'],
       $this->users['d'],
@@ -97,12 +97,6 @@ class InboxBlockTest extends WebDriverTestBase {
       $this->users['c'],
     ], $this->users['c']);
 
-    $this->createThreadWithMessages([
-      $this->users['a'],
-      $this->users['b'],
-      $this->users['d'],
-    ], $this->users['d']);
-
     $settings = [
       'thread_count' => 1,
       'ajax_load_count' => 2,
@@ -123,7 +117,7 @@ class InboxBlockTest extends WebDriverTestBase {
       ->getPage()
       ->clickLink('Load Previous');
     $this->assertSession()->assertWaitOnAjaxRequest();
-    $this->assertEquals(5, $this->countThreads());
+    $this->assertEquals(4, $this->countThreads());
     $this->assertSession()
       ->pageTextNotContains('Load Previous');
   }
-- 
GitLab


From ac738cb03cfc02d1f9c2d53fb2acbce20c7abd01 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Thu, 5 Dec 2024 20:26:08 +0100
Subject: [PATCH 21/23] Fix eslint

---
 js/private_message_inbox_block.js | 3 +--
 js/private_message_utils.js       | 7 ++-----
 2 files changed, 3 insertions(+), 7 deletions(-)

diff --git a/js/private_message_inbox_block.js b/js/private_message_inbox_block.js
index 72132723..929ce33e 100644
--- a/js/private_message_inbox_block.js
+++ b/js/private_message_inbox_block.js
@@ -84,8 +84,7 @@
      *   HTML content of threads.
      */
     insertPreviousThreads(threadsHtml) {
-      const newNodes =
-        Drupal.PrivateMessageUtils.parseHTML(threadsHtml);
+      const newNodes = Drupal.PrivateMessageUtils.parseHTML(threadsHtml);
 
       Array.from(newNodes).forEach((node) => {
         const appendedElement = this.container.appendChild(node);
diff --git a/js/private_message_utils.js b/js/private_message_utils.js
index 4480cf5d..4ecdf637 100644
--- a/js/private_message_utils.js
+++ b/js/private_message_utils.js
@@ -10,17 +10,14 @@
      *
      * @param {string} html
      *   The HTML string to parse.
-     * @returns {NodeListOf<ChildNode>}
+     * @return {NodeListOf<ChildNode>}
      *   Parsed HTML content as NodeList.
      */
     parseHTML(html) {
       const tempDiv = document.createElement('div');
       tempDiv.innerHTML = html;
 
-      return document
-        .createDocumentFragment()
-        .appendChild(tempDiv)
-        .childNodes;
+      return document.createDocumentFragment().appendChild(tempDiv).childNodes;
     },
 
     /**
-- 
GitLab


From f0d95c045fb6bb1121144b78cdce3eeecd04d64a Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Thu, 5 Dec 2024 20:43:29 +0100
Subject: [PATCH 22/23] Improve test.

---
 .../FunctionalJavascript/InboxBlockTest.php   | 29 +++++++++++++++----
 1 file changed, 24 insertions(+), 5 deletions(-)

diff --git a/tests/src/FunctionalJavascript/InboxBlockTest.php b/tests/src/FunctionalJavascript/InboxBlockTest.php
index bc405695..5f68a3d9 100644
--- a/tests/src/FunctionalJavascript/InboxBlockTest.php
+++ b/tests/src/FunctionalJavascript/InboxBlockTest.php
@@ -86,7 +86,7 @@ class InboxBlockTest extends WebDriverTestBase {
    */
   public function testLoadPrevious(): void {
     // 2 more threads, together we have 4.
-    $this->threads[] = $this->createThreadWithMessages([
+    $this->createThreadWithMessages([
       $this->users['a'],
       $this->users['d'],
     ], $this->users['d']);
@@ -133,7 +133,12 @@ class InboxBlockTest extends WebDriverTestBase {
     $this->drupalPlaceBlock('private_message_inbox_block', $settings);
     $this->drupalLogin($this->users['a']);
 
-    $lastThread = $this->createThreadWithMessages([
+    $this->threads[] = $this->createThreadWithMessages([
+      $this->users['a'],
+      $this->users['d'],
+    ], $this->users['d']);
+
+    $this->threads[] = $this->createThreadWithMessages([
       $this->users['a'],
       $this->users['b'],
       $this->users['c'],
@@ -141,9 +146,23 @@ class InboxBlockTest extends WebDriverTestBase {
 
     $this->assertSession()->assertWaitOnAjaxRequest();
 
-    $this->assertSession()
-      ->elementExists('css', '.private-message-thread-inbox[data-thread-id="' . $lastThread->id() . '"]');
-    $this->assertEquals(3, $this->countThreads());
+    $this->assertEquals(4, $this->countThreads());
+
+    $threadElements = $this->getSession()
+      ->getPage()
+      ->findAll('css', '.private-message-thread-inbox');
+    $threadIds = [];
+    foreach ($threadElements as $threadElement) {
+      $threadIds[] = $threadElement->getAttribute('data-thread-id');
+    }
+
+    $expectedOrder = [];
+    foreach ($this->threads as $thread) {
+      $expectedOrder[] = $thread->id();
+    }
+    $expectedOrder = array_reverse($expectedOrder);
+
+    $this->assertEquals($expectedOrder, $threadIds, 'Threads are not in the expected order.');
   }
 
 }
-- 
GitLab


From a66d4b43b213dff7b1f1710acbe4953dfc98ce09 Mon Sep 17 00:00:00 2001
From: Adrian Lorenc <adrian.lorenc@gmail.com>
Date: Thu, 5 Dec 2024 20:52:55 +0100
Subject: [PATCH 23/23] Always convert to an array

---
 js/private_message_inbox_block.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/js/private_message_inbox_block.js b/js/private_message_inbox_block.js
index 929ce33e..e36d9aa7 100644
--- a/js/private_message_inbox_block.js
+++ b/js/private_message_inbox_block.js
@@ -152,7 +152,7 @@
           const newThreadContent = Drupal.PrivateMessageUtils.parseHTML(
             newThreads[threadId],
           );
-          newThreadContent.forEach((child) => {
+          Array.from(newThreadContent).forEach((child) => {
             const appendedElement = this.container.appendChild(child);
             Drupal.attachBehaviors(appendedElement);
           });
-- 
GitLab