update notification text using sid in web app
This commit is contained in:
@@ -70,6 +70,8 @@
|
|||||||
"notifications_delete": "Delete",
|
"notifications_delete": "Delete",
|
||||||
"notifications_copied_to_clipboard": "Copied to clipboard",
|
"notifications_copied_to_clipboard": "Copied to clipboard",
|
||||||
"notifications_tags": "Tags",
|
"notifications_tags": "Tags",
|
||||||
|
"notifications_sid": "Sequence ID",
|
||||||
|
"notifications_revisions": "Revisions",
|
||||||
"notifications_priority_x": "Priority {{priority}}",
|
"notifications_priority_x": "Priority {{priority}}",
|
||||||
"notifications_new_indicator": "New notification",
|
"notifications_new_indicator": "New notification",
|
||||||
"notifications_attachment_image": "Attachment image",
|
"notifications_attachment_image": "Attachment image",
|
||||||
|
|||||||
@@ -23,9 +23,17 @@ const broadcastChannel = new BroadcastChannel("web-push-broadcast");
|
|||||||
|
|
||||||
const addNotification = async ({ subscriptionId, message }) => {
|
const addNotification = async ({ subscriptionId, message }) => {
|
||||||
const db = await dbAsync();
|
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({
|
await db.notifications.add({
|
||||||
...message,
|
...populatedMessage,
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
// New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
|
// New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
|
||||||
new: 1,
|
new: 1,
|
||||||
|
|||||||
@@ -156,18 +156,41 @@ class SubscriptionManager {
|
|||||||
// It's actually fine, because the reading and filtering is quite fast. The rendering is what's
|
// 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
|
// killing performance. See https://dexie.org/docs/Collection/Collection.offset()#a-better-paging-approach
|
||||||
|
|
||||||
return this.db.notifications
|
const notifications = await this.db.notifications
|
||||||
.orderBy("time") // Sort by time first
|
.orderBy("mtime") // Sort by time first
|
||||||
.filter((n) => n.subscriptionId === subscriptionId)
|
.filter((n) => n.subscriptionId === subscriptionId)
|
||||||
.reverse()
|
.reverse()
|
||||||
.toArray();
|
.toArray();
|
||||||
|
|
||||||
|
return this.groupNotificationsBySID(notifications);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllNotifications() {
|
async getAllNotifications() {
|
||||||
return this.db.notifications
|
const notifications = await this.db.notifications
|
||||||
.orderBy("time") // Efficient, see docs
|
.orderBy("mtime") // Efficient, see docs
|
||||||
.reverse()
|
.reverse()
|
||||||
.toArray();
|
.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 */
|
/** Adds notification, or returns false if it already exists */
|
||||||
@@ -177,9 +200,16 @@ class SubscriptionManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
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
|
// sw.js duplicates this logic, so if you change it here, change it there too
|
||||||
await this.db.notifications.add({
|
await this.db.notifications.add({
|
||||||
...notification,
|
...populatedNotification,
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
// New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
|
// New marker (used for bubble indicator); cannot be boolean; Dexie index limitation
|
||||||
new: 1,
|
new: 1,
|
||||||
@@ -195,7 +225,16 @@ class SubscriptionManager {
|
|||||||
|
|
||||||
/** Adds/replaces notifications, will not throw if they exist */
|
/** Adds/replaces notifications, will not throw if they exist */
|
||||||
async addNotifications(subscriptionId, notifications) {
|
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;
|
const lastNotificationId = notifications.at(-1).id;
|
||||||
await this.db.notifications.bulkPut(notificationsWithSubscriptionId);
|
await this.db.notifications.bulkPut(notificationsWithSubscriptionId);
|
||||||
await this.db.subscriptions.update(subscriptionId, {
|
await this.db.subscriptions.update(subscriptionId, {
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ const createDatabase = (username) => {
|
|||||||
const dbName = username ? `ntfy-${username}` : "ntfy"; // IndexedDB database is based on the logged-in user
|
const dbName = username ? `ntfy-${username}` : "ntfy"; // IndexedDB database is based on the logged-in user
|
||||||
const db = new Dexie(dbName);
|
const db = new Dexie(dbName);
|
||||||
|
|
||||||
db.version(2).stores({
|
db.version(3).stores({
|
||||||
subscriptions: "&id,baseUrl,[baseUrl+mutedUntil]",
|
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",
|
users: "&baseUrl,username",
|
||||||
prefs: "&key",
|
prefs: "&key",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -53,6 +53,14 @@ export const badge = "/static/images/mask-icon.svg";
|
|||||||
export const toNotificationParams = ({ subscriptionId, message, defaultTitle, topicRoute }) => {
|
export const toNotificationParams = ({ subscriptionId, message, defaultTitle, topicRoute }) => {
|
||||||
const image = isImage(message.attachment) ? message.attachment.url : undefined;
|
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
|
// https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API
|
||||||
return [
|
return [
|
||||||
formatTitleWithDefault(message, defaultTitle),
|
formatTitleWithDefault(message, defaultTitle),
|
||||||
@@ -61,8 +69,8 @@ export const toNotificationParams = ({ subscriptionId, message, defaultTitle, to
|
|||||||
badge,
|
badge,
|
||||||
icon,
|
icon,
|
||||||
image,
|
image,
|
||||||
timestamp: message.time * 1_000,
|
timestamp: message.mtime,
|
||||||
tag: subscriptionId,
|
tag,
|
||||||
renotify: true,
|
renotify: true,
|
||||||
silent: false,
|
silent: false,
|
||||||
// This is used by the notification onclick event
|
// This is used by the notification onclick event
|
||||||
|
|||||||
@@ -233,10 +233,20 @@ const NotificationItem = (props) => {
|
|||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
console.log(`[Notifications] Deleting notification ${notification.id}`);
|
console.log(`[Notifications] Deleting notification ${notification.id}`);
|
||||||
await subscriptionManager.deleteNotification(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 () => {
|
const handleMarkRead = async () => {
|
||||||
console.log(`[Notifications] Marking notification ${notification.id} as read`);
|
console.log(`[Notifications] Marking notification ${notification.id} as read`);
|
||||||
await subscriptionManager.markNotificationRead(notification.id);
|
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) => {
|
const handleCopy = (s) => {
|
||||||
navigator.clipboard.writeText(s);
|
navigator.clipboard.writeText(s);
|
||||||
@@ -248,6 +258,8 @@ const NotificationItem = (props) => {
|
|||||||
const hasUserActions = notification.actions && notification.actions.length > 0;
|
const hasUserActions = notification.actions && notification.actions.length > 0;
|
||||||
const showActions = hasAttachmentActions || hasClickAction || hasUserActions;
|
const showActions = hasAttachmentActions || hasClickAction || hasUserActions;
|
||||||
|
|
||||||
|
const showSid = notification.id !== notification.sid || notification.history;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card sx={{ padding: 1 }} role="listitem" aria-label={t("notifications_list_item")}>
|
<Card sx={{ padding: 1 }} role="listitem" aria-label={t("notifications_list_item")}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@@ -304,6 +316,16 @@ const NotificationItem = (props) => {
|
|||||||
{t("notifications_tags")}: {tags}
|
{t("notifications_tags")}: {tags}
|
||||||
</Typography>
|
</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>
|
</CardContent>
|
||||||
{showActions && (
|
{showActions && (
|
||||||
<CardActions sx={{ paddingTop: 0 }}>
|
<CardActions sx={{ paddingTop: 0 }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user