Refresh the mobile unread indicator on DOM update
authorAlexander Ebert <ebert@woltlab.com>
Thu, 23 Jun 2022 16:56:28 +0000 (18:56 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Thu, 23 Jun 2022 16:56:28 +0000 (18:56 +0200)
The unread indicator might become out of sync if the menu is closed, but an action causes the last remaining element(s) to be marked as read.

Using a `MutationObserver` removes the need of signaling such change directly by all actions, for example a “Mark All as Read” button.

See https://www.woltlab.com/community/thread/296063-alle-als-gelesen-markieren-entfernt-badge-nicht/

ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Main.js

index ffd827d66bf5526e429030604bcf92306dca183d..d407aaaec19bce066b592ac265d9afdc52c9b45c 100644 (file)
@@ -20,6 +20,7 @@ export class PageMenuMain implements PageMenuProvider {
   private readonly container: PageMenuContainer;
   private readonly mainMenu: HTMLElement;
   private readonly menuItemProvider: PageMenuMainProvider;
+  private readonly observer: MutationObserver;
 
   constructor(menuItemProvider: PageMenuMainProvider) {
     this.mainMenu = document.querySelector(".mainMenu")!;
@@ -33,6 +34,22 @@ export class PageMenuMain implements PageMenuProvider {
 
       this.container.toggle();
     };
+
+    this.observer = new MutationObserver((mutations) => {
+      let refreshUnreadIndicator = false;
+
+      mutations.forEach((mutation) => {
+        if (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0 || mutation.type === "characterData") {
+          refreshUnreadIndicator = true;
+        }
+      });
+
+      if (refreshUnreadIndicator) {
+        this.refreshUnreadIndicator();
+      }
+    });
+
+    this.watchForChanges();
   }
 
   enable(): void {
@@ -84,13 +101,22 @@ export class PageMenuMain implements PageMenuProvider {
   }
 
   sleep(): void {
-    /* Does nothing */
+    this.watchForChanges();
   }
 
   wakeup(): void {
+    this.observer.disconnect();
+
     this.refreshUnreadIndicator();
   }
 
+  private watchForChanges(): void {
+    this.observer.observe(this.mainMenu, {
+      childList: true,
+      subtree: true,
+    });
+  }
+
   private buildMainMenu(): HTMLElement {
     const boxMenu = this.mainMenu.querySelector(".boxMenu") as HTMLElement;
 
index 3e050ab6604f887978c3fa3ce4c713bc12238851..962fa9c6cfd0223603932f1327594bc8543e45c2 100644 (file)
@@ -23,6 +23,18 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
                 event.stopPropagation();
                 this.container.toggle();
             };
+            this.observer = new MutationObserver((mutations) => {
+                let refreshUnreadIndicator = false;
+                mutations.forEach((mutation) => {
+                    if (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0 || mutation.type === "characterData") {
+                        refreshUnreadIndicator = true;
+                    }
+                });
+                if (refreshUnreadIndicator) {
+                    this.refreshUnreadIndicator();
+                }
+            });
+            this.watchForChanges();
         }
         enable() {
             this.mainMenu.setAttribute("aria-expanded", "false");
@@ -62,11 +74,18 @@ define(["require", "exports", "tslib", "./Container", "../../../Language", "../.
             return this.mainMenu;
         }
         sleep() {
-            /* Does nothing */
+            this.watchForChanges();
         }
         wakeup() {
+            this.observer.disconnect();
             this.refreshUnreadIndicator();
         }
+        watchForChanges() {
+            this.observer.observe(this.mainMenu, {
+                childList: true,
+                subtree: true,
+            });
+        }
         buildMainMenu() {
             const boxMenu = this.mainMenu.querySelector(".boxMenu");
             const nav = this.buildMenu(boxMenu);