14 KiB
Notifications Mode for post.html
Overview
Add a notifications=true URL parameter to post.html that transforms the page into a notifications view. Notification events are rendered as full post cards using the existing renderPostItem() from post-interactions.mjs, preserving all interaction capabilities — liking, replying, quoting, zapping, and commenting — on every notification item.
URL Parameter
post.html?notifications=true
Added to the existing parameter set documented in the post.html header comment:
| Parameter | Values | Default | Description |
|---|---|---|---|
notifications |
true/false |
false |
Show notifications directed at the current user |
When notifications=true:
postbox is hidden (impliespost=false)followsandviewedare ignorednpubis ignored (notifications are always for the logged-in user)- Header text changes to NOTIFICATIONS
- Footer shows notification count instead of post count
Data Flow
flowchart TD
A[Page Load] --> B[Parse URL: notifications=true]
B --> C[initNDKPage + getPubkey]
C --> D[Subscribe: kinds 1,6,7,9735 with #p = currentPubkey]
C --> E[fetchEventsFromAllRelays for initial batch]
D --> F[ndkEvent listener]
E --> F
F --> G{Is notification for me?}
G -->|No: own event or not tagged| H[Discard]
G -->|Yes| I[Fetch referenced post if needed]
I --> J[Store in notifications array]
J --> K[Render as full post card via renderPostItem]
K --> L[Append interaction bar: like, reply, quote, zap, comment]
Rendering Approach — Full Post Cards
The key design decision: notifications render the referenced post as a full interactive card, not a compact notification summary. Each notification shows:
- A notification header — small banner above the post card showing who did what
- Avatar + name of the actor
- Action type badge:
Lliked,Rreposted,Creplied,Zzapped,@mentioned - Relative timestamp
- The referenced post — rendered via
renderPostItem()with full interaction bar- For reactions/reposts/zaps: shows YOUR original post that was interacted with
- For replies: shows the REPLY itself as the main card, with your original post as a compact reference below
- For mentions: shows the mentioning post as the main card
This means every notification card supports:
- Liking (via
publishLike()) - Replying (via inline composer +
handleComposerCommentIntent()) - Quoting (via inline composer +
handleComposerQuoteIntent()) - Zapping (via interaction bar)
- Viewing/toggling comments (via
renderCommentThread())
Card Layout
┌─────────────────────────────────────────────────┐
│ 🔔 Alice liked your post 2m │ ← notification header
├─────────────────────────────────────────────────┤
│ [Your Avatar] You │ ← standard post card
│ │
│ Your original post content here... │
│ │
│ [Like] [Reply] [Quote] [Zap] [Time] │ ← full interaction bar
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ 🔔 Bob replied to your post 5m │ ← notification header
├─────────────────────────────────────────────────┤
│ [Bob Avatar] Bob │ ← reply post card
│ │
│ Bob reply content here... │
│ │
│ [Like] [Reply] [Quote] [Zap] [Time] │ ← full interaction bar
│ ┌─────────────────────────────────────────────┐ │
│ │ In reply to: │ │ ← compact reference
│ │ Your original post snippet... │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
Implementation Details
1. URL Parameter Parsing
Add to the existing parameter parsing block at line 648:
const notificationsParam = urlParams.get('notifications');
const showNotifications = notificationsParam === 'true';
Mode precedence: notifications overrides viewed, follows, profile, and post:
const showNotifications = notificationsParam === 'true';
const showViewed = showNotifications ? false : (viewedParam === 'true');
const showProfile = showNotifications ? false : (profileParam === 'true');
const showPostBox = showNotifications ? false : (showViewed ? false : (postParam !== 'false'));
const showFollows = showNotifications ? false : (showViewed ? true : (followsParam === 'true'));
2. Notification State Variables
// Notifications mode state
const NOTIF_KINDS = [1, 6, 7, 9735];
const MAX_NOTIFICATIONS = 300;
let notifications = [];
let notificationIds = new Set();
let referencedPosts = new Map(); // eventId -> event (your original posts)
let notifRenderCount = 20;
3. Notification Subscription
In the main() function, when showNotifications is true:
if (showNotifications) {
document.title = 'NOTIFICATIONS';
headerText.textContent = 'NOTIFICATIONS';
// Subscribe to events tagging the current user
subscribe(
{ kinds: NOTIF_KINDS, '#p': [currentPubkey], limit: 50 },
{ closeOnEose: false, cacheUsage: 'CACHE_FIRST' }
);
// Also do a direct fetch for initial population
fetchEventsFromAllRelays({
kinds: NOTIF_KINDS, '#p': [currentPubkey], limit: 50
});
}
4. Event Processing in ndkEvent Handler
Add a new branch in the ndkEvent listener for notifications mode:
if (showNotifications && NOTIF_KINDS.includes(evt.kind)) {
// Filter: skip own events, skip duplicates
if (evt.pubkey === currentPubkey) return;
if (notificationIds.has(evt.id)) return;
// For kind 1: only keep if current user is in p-tags
if (evt.kind === 1) {
const pTags = evt.tags?.filter(t => t[0] === 'p').map(t => t[1]) || [];
if (!pTags.includes(currentPubkey)) return;
}
// Fetch referenced post for context
await ensureReferencedPost(evt);
// For reactions/reposts/zaps: only keep if referenced post is ours
if ([6, 7, 9735].includes(evt.kind)) {
const refId = getRootEventId(evt);
const refEvent = referencedPosts.get(refId);
if (!refEvent || refEvent.pubkey !== currentPubkey) return;
}
notificationIds.add(evt.id);
notifications.push(evt);
pruneNotifications();
renderNotificationsFeed();
}
5. Rendering Notifications
function createNotificationElement(notifEvent) {
const wrapper = document.createElement('div');
wrapper.className = 'divNotifWrapper';
// 1. Notification header (who did what)
const header = createNotifHeader(notifEvent);
wrapper.appendChild(header);
// 2. The main post card — determine which event to render
let mainEvent;
if (notifEvent.kind === 1) {
// Reply/mention: show the reply itself
mainEvent = notifEvent;
} else {
// Like/repost/zap: show the referenced (your) post
const refId = getRootEventId(notifEvent);
mainEvent = referencedPosts.get(refId) || notifEvent;
}
// Render as full interactive post card
const postEl = interactions.renderPostItem(mainEvent, {
currentPubkey,
showHeader: true,
isCompact: false
});
wrapper.appendChild(postEl);
// 3. For replies: add compact reference to original post
if (notifEvent.kind === 1) {
const refId = getRootEventId(notifEvent);
const refEvent = referencedPosts.get(refId);
if (refEvent) {
const refSnippet = createCompactReference(refEvent);
wrapper.appendChild(refSnippet);
}
}
return wrapper;
}
6. Notification Header CSS
.divNotifWrapper {
margin-bottom: 15px;
}
.divNotifHeader {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
font-size: 80%;
color: var(--muted-color);
border: 1px solid var(--muted-color);
border-bottom: none;
border-radius: 10px 10px 0 0;
background: var(--secondary-color);
}
.divNotifHeader .notif-avatar {
width: 20px;
height: 20px;
border-radius: 3px;
object-fit: cover;
}
.divNotifHeader .notif-type-badge {
border: 1px solid var(--muted-color);
color: var(--accent-color);
border-radius: 999px;
min-width: 18px;
height: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 75%;
font-weight: bold;
padding: 0 4px;
}
.divNotifWrapper .divPostItem {
border-radius: 0 0 10px 10px;
margin-bottom: 0;
}
7. See More Button
Reuse the existing btnSeeMore element. In notifications mode, wire it to:
- First expand
notifRenderCountif more notifications exist in memory - Then fetch older notifications with
untilparameter
8. Footer Update
In the UpdateFooter function, add notifications branch:
if (showNotifications) {
divFooterRight.innerHTML = `${notifications.length} notifications`;
}
9. Sidenav Link Update
Change the existing notifications link in the sidenav from:
<a href="./notifications.html">NOTIFICATIONS</a>
to:
<a href="./post.html?notifications=true">NOTIFICATIONS</a>
10. Interaction Subscriptions for Notification Posts
After rendering notification cards, subscribe to interactions on the rendered posts so like/reply/quote/zap counts update in real-time — same pattern as the existing renderFeed() function that calls fetchExistingInteractions() and subscribeToInteractions().
11. Inline Reply Composer
The existing handleComposerCommentIntent() and handleComposerQuoteIntent() functions work by finding .divPostItem[data-post-id="..."] in the DOM. Since notification cards contain standard post items rendered by renderPostItem(), the inline composer will work automatically — no changes needed.
However, since showPostBox is false in notifications mode, the inline composers won't mount. We need to override this: in notifications mode, allow inline composers on notification post cards even though the top-level post box is hidden. This requires a small adjustment to getOrCreateInlineComposer() to check showNotifications || showPostBox.
Index Page — LOVE Card
Add a new card to the arrApps array in index.html called LOVE that links to post.html?notifications=true.
The card follows the same pattern as feed and view which have special URL mappings at line 594-598. Add a new condition for love:
{ id: `love`, svg: ``, name: `LOVE` },
And in the URL mapping logic:
const url = Each.id === 'feed'
? './post.html?follows=true&post=false'
: Each.id === 'view'
? './post.html?follows=true&post=false&viewed=true'
: Each.id === 'love'
? './post.html?notifications=true'
: `./${Each.id}.html`;
The SVG is left empty for now as requested.
Files Modified
| File | Changes |
|---|---|
www/post.html |
Add notifications URL param, notification state, subscription, event handler branch, rendering functions, CSS, sidenav link update |
www/index.html |
Add LOVE card to arrApps array, add URL mapping for love ID |
No new files needed. All logic stays inline following the existing patterns.
Implementation Steps
- Add
notifications=trueURL parameter parsing and mode flags inpost.html - Add notification-specific CSS styles (notification header, wrapper)
- Add notification state variables and helper functions
- Add notification subscription in
main()initialization - Add notification event processing branch in
ndkEventlistener - Add notification rendering functions (createNotificationElement, renderNotificationsFeed, createNotifHeader, createCompactReference)
- Wire See More button for notifications mode
- Update footer to show notification count
- Update sidenav link to use
post.html?notifications=true - Allow inline composers in notifications mode (adjust showPostBox guard)
- Wire interaction subscriptions for rendered notification post cards
- Update the post.html header comment to document the new parameter
- Add LOVE card to
arrAppsarray inindex.htmlwith empty SVG - Add URL mapping for
love→post.html?notifications=trueinindex.html