Преглед изворни кода

update notification text using sid in web app

Hunter Kehoe пре 4 месеци
родитељ
комит
8293a24cf9

+ 2 - 0
web/public/static/langs/en.json

@@ -70,6 +70,8 @@
   "notifications_delete": "Delete",
   "notifications_copied_to_clipboard": "Copied to clipboard",
   "notifications_tags": "Tags",
+  "notifications_sid": "Sequence ID",
+  "notifications_revisions": "Revisions",
   "notifications_priority_x": "Priority {{priority}}",
   "notifications_new_indicator": "New notification",
   "notifications_attachment_image": "Attachment image",

+ 9 - 1
web/public/sw.js

@@ -23,9 +23,17 @@ const broadcastChannel = new BroadcastChannel("web-push-broadcast");
 
 const addNotification = async ({ subscriptionId, message }) => {
   const db = await dbAsync();
+  const populatedMessage = message;
+
+  if (!("mtime" in populatedMessage)) {
+    populatedMessage.mtime = message.time * 1000;
+  }
+  if (!("sid" in populatedMessage)) {
+    populatedMessage.sid = message.id;
+  }
 
   await db.notifications.add({
-    ...message,
+    ...populatedMessage,
     subscriptionId,
     // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
     new: 1,

+ 45 - 6
web/src/app/SubscriptionManager.js

@@ -156,18 +156,41 @@ class SubscriptionManager {
     // It's actually fine, because the reading and filtering is quite fast. The rendering is what's
     // killing performance. See  https://dexie.org/docs/Collection/Collection.offset()#a-better-paging-approach
 
-    return this.db.notifications
-      .orderBy("time") // Sort by time first
+    const notifications = await this.db.notifications
+      .orderBy("mtime") // Sort by time first
       .filter((n) => n.subscriptionId === subscriptionId)
       .reverse()
       .toArray();
+
+    return this.groupNotificationsBySID(notifications);
   }
 
   async getAllNotifications() {
-    return this.db.notifications
-      .orderBy("time") // Efficient, see docs
+    const notifications = await this.db.notifications
+      .orderBy("mtime") // Efficient, see docs
       .reverse()
       .toArray();
+
+    return this.groupNotificationsBySID(notifications);
+  }
+
+  // Collapse notification updates based on sids
+  groupNotificationsBySID(notifications) {
+    const results = {};
+    notifications.forEach((notification) => {
+      const key = `${notification.subscriptionId}:${notification.sid}`;
+      if (key in results) {
+        if ("history" in results[key]) {
+          results[key].history.push(notification);
+        } else {
+          results[key].history = [notification];
+        }
+      } else {
+        results[key] = notification;
+      }
+    });
+
+    return Object.values(results);
   }
 
   /** Adds notification, or returns false if it already exists */
@@ -177,9 +200,16 @@ class SubscriptionManager {
       return false;
     }
     try {
+      const populatedNotification = notification;
+      if (!("mtime" in populatedNotification)) {
+        populatedNotification.mtime = notification.time * 1000;
+      }
+      if (!("sid" in populatedNotification)) {
+        populatedNotification.sid = notification.id;
+      }
       // sw.js duplicates this logic, so if you change it here, change it there too
       await this.db.notifications.add({
-        ...notification,
+        ...populatedNotification,
         subscriptionId,
         // New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
         new: 1,
@@ -195,7 +225,16 @@ class SubscriptionManager {
 
   /** Adds/replaces notifications, will not throw if they exist */
   async addNotifications(subscriptionId, notifications) {
-    const notificationsWithSubscriptionId = notifications.map((notification) => ({ ...notification, subscriptionId }));
+    const notificationsWithSubscriptionId = notifications.map((notification) => {
+      const populatedNotification = notification;
+      if (!("mtime" in populatedNotification)) {
+        populatedNotification.mtime = notification.time * 1000;
+      }
+      if (!("sid" in populatedNotification)) {
+        populatedNotification.sid = notification.id;
+      }
+      return { ...populatedNotification, subscriptionId };
+    });
     const lastNotificationId = notifications.at(-1).id;
     await this.db.notifications.bulkPut(notificationsWithSubscriptionId);
     await this.db.subscriptions.update(subscriptionId, {

+ 2 - 2
web/src/app/db.js

@@ -11,9 +11,9 @@ const createDatabase = (username) => {
   const dbName = username ? `ntfy-${username}` : "ntfy"; // IndexedDB database is based on the logged-in user
   const db = new Dexie(dbName);
 
-  db.version(2).stores({
+  db.version(3).stores({
     subscriptions: "&id,baseUrl,[baseUrl+mutedUntil]",
-    notifications: "&id,subscriptionId,time,new,[subscriptionId+new]", // compound key for query performance
+    notifications: "&id,sid,subscriptionId,time,mtime,new,[subscriptionId+new]", // compound key for query performance
     users: "&baseUrl,username",
     prefs: "&key",
   });

+ 10 - 2
web/src/app/notificationUtils.js

@@ -53,6 +53,14 @@ export const badge = "/static/images/mask-icon.svg";
 export const toNotificationParams = ({ subscriptionId, message, defaultTitle, topicRoute }) => {
   const image = isImage(message.attachment) ? message.attachment.url : undefined;
 
+  let tag;
+
+  if (message.sid) {
+    tag = message.sid;
+  } else {
+    tag = subscriptionId;
+  }
+
   // https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API
   return [
     formatTitleWithDefault(message, defaultTitle),
@@ -61,8 +69,8 @@ export const toNotificationParams = ({ subscriptionId, message, defaultTitle, to
       badge,
       icon,
       image,
-      timestamp: message.time * 1_000,
-      tag: subscriptionId,
+      timestamp: message.mtime,
+      tag,
       renotify: true,
       silent: false,
       // This is used by the notification onclick event

+ 22 - 0
web/src/components/Notifications.jsx

@@ -233,10 +233,20 @@ const NotificationItem = (props) => {
   const handleDelete = async () => {
     console.log(`[Notifications] Deleting notification ${notification.id}`);
     await subscriptionManager.deleteNotification(notification.id);
+    notification.history?.forEach(async (revision) => {
+      console.log(`[Notifications] Deleting revision ${revision.id}`);
+      await subscriptionManager.deleteNotification(revision.id);
+    });
   };
   const handleMarkRead = async () => {
     console.log(`[Notifications] Marking notification ${notification.id} as read`);
     await subscriptionManager.markNotificationRead(notification.id);
+    notification.history
+      ?.filter((revision) => revision.new === 1)
+      .forEach(async (revision) => {
+        console.log(`[Notifications] Marking revision ${revision.id} as read`);
+        await subscriptionManager.markNotificationRead(revision.id);
+      });
   };
   const handleCopy = (s) => {
     navigator.clipboard.writeText(s);
@@ -248,6 +258,8 @@ const NotificationItem = (props) => {
   const hasUserActions = notification.actions && notification.actions.length > 0;
   const showActions = hasAttachmentActions || hasClickAction || hasUserActions;
 
+  const showSid = notification.id !== notification.sid || notification.history;
+
   return (
     <Card sx={{ padding: 1 }} role="listitem" aria-label={t("notifications_list_item")}>
       <CardContent>
@@ -304,6 +316,16 @@ const NotificationItem = (props) => {
             {t("notifications_tags")}: {tags}
           </Typography>
         )}
+        {showSid && (
+          <Typography sx={{ fontSize: 14 }} color="text.secondary">
+            {t("notifications_sid")}: {notification.sid}
+          </Typography>
+        )}
+        {notification.history && (
+          <Typography sx={{ fontSize: 14 }} color="text.secondary">
+            {t("notifications_revisions")}: {notification.history.length + 1}
+          </Typography>
+        )}
       </CardContent>
       {showActions && (
         <CardActions sx={{ paddingTop: 0 }}>