357 lines
14 KiB
Markdown
357 lines
14 KiB
Markdown
# Notifications Mode for `post.html`
|
|
|
|
## Overview
|
|
|
|
Add a `notifications=true` URL parameter to [`post.html`](../www/post.html) that transforms the page into a notifications view. Notification events are rendered as full post cards using the existing [`renderPostItem()`](../www/js/post-interactions.mjs:549) 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](../www/post.html:3):
|
|
|
|
| Parameter | Values | Default | Description |
|
|
|-----------|--------|---------|-------------|
|
|
| `notifications` | `true`/`false` | `false` | Show notifications directed at the current user |
|
|
|
|
When `notifications=true`:
|
|
- `post` box is hidden (implies `post=false`)
|
|
- `follows` and `viewed` are ignored
|
|
- `npub` is ignored (notifications are always for the logged-in user)
|
|
- Header text changes to **NOTIFICATIONS**
|
|
- Footer shows notification count instead of post count
|
|
|
|
## Data Flow
|
|
|
|
```mermaid
|
|
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:
|
|
|
|
1. **A notification header** — small banner above the post card showing who did what
|
|
- Avatar + name of the actor
|
|
- Action type badge: `L` liked, `R` reposted, `C` replied, `Z` zapped, `@` mentioned
|
|
- Relative timestamp
|
|
2. **The referenced post** — rendered via [`renderPostItem()`](../www/js/post-interactions.mjs:549) 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()`](../www/js/post-interactions.mjs:1724))
|
|
- Replying (via inline composer + [`handleComposerCommentIntent()`](../www/post.html:1794))
|
|
- Quoting (via inline composer + [`handleComposerQuoteIntent()`](../www/post.html:1809))
|
|
- Zapping (via interaction bar)
|
|
- Viewing/toggling comments (via [`renderCommentThread()`](../www/js/post-interactions.mjs:1336))
|
|
|
|
### 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](../www/post.html:648):
|
|
|
|
```javascript
|
|
const notificationsParam = urlParams.get('notifications');
|
|
const showNotifications = notificationsParam === 'true';
|
|
```
|
|
|
|
Mode precedence: `notifications` overrides `viewed`, `follows`, `profile`, and `post`:
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
// 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:
|
|
|
|
```javascript
|
|
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](../www/post.html:2098) for notifications mode:
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
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
|
|
|
|
```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`](../www/post.html:590) element. In notifications mode, wire it to:
|
|
1. First expand `notifRenderCount` if more notifications exist in memory
|
|
2. Then fetch older notifications with `until` parameter
|
|
|
|
### 8. Footer Update
|
|
|
|
In the [`UpdateFooter`](../www/post.html:1997) function, add notifications branch:
|
|
|
|
```javascript
|
|
if (showNotifications) {
|
|
divFooterRight.innerHTML = `${notifications.length} notifications`;
|
|
}
|
|
```
|
|
|
|
### 9. Sidenav Link Update
|
|
|
|
Change the existing [notifications link in the sidenav](../www/post.html:609) from:
|
|
```html
|
|
<a href="./notifications.html">NOTIFICATIONS</a>
|
|
```
|
|
to:
|
|
```html
|
|
<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()`](../www/post.html:1641) function that calls [`fetchExistingInteractions()`](../www/js/post-interactions.mjs:1813) and [`subscribeToInteractions()`](../www/js/post-interactions.mjs:1462).
|
|
|
|
### 11. Inline Reply Composer
|
|
|
|
The existing [`handleComposerCommentIntent()`](../www/post.html:1794) and [`handleComposerQuoteIntent()`](../www/post.html:1809) 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()`](../www/post.html:1755) to check `showNotifications || showPostBox`.
|
|
|
|
## Index Page — LOVE Card
|
|
|
|
Add a new card to the [`arrApps`](../www/index.html:420) array in [`index.html`](../www/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](../www/index.html:594). Add a new condition for `love`:
|
|
|
|
```javascript
|
|
{ id: `love`, svg: ``, name: `LOVE` },
|
|
```
|
|
|
|
And in the URL mapping logic:
|
|
|
|
```javascript
|
|
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`](../www/post.html) | Add `notifications` URL param, notification state, subscription, event handler branch, rendering functions, CSS, sidenav link update |
|
|
| [`www/index.html`](../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
|
|
|
|
1. Add `notifications=true` URL parameter parsing and mode flags in `post.html`
|
|
2. Add notification-specific CSS styles (notification header, wrapper)
|
|
3. Add notification state variables and helper functions
|
|
4. Add notification subscription in `main()` initialization
|
|
5. Add notification event processing branch in `ndkEvent` listener
|
|
6. Add notification rendering functions (createNotificationElement, renderNotificationsFeed, createNotifHeader, createCompactReference)
|
|
7. Wire See More button for notifications mode
|
|
8. Update footer to show notification count
|
|
9. Update sidenav link to use `post.html?notifications=true`
|
|
10. Allow inline composers in notifications mode (adjust showPostBox guard)
|
|
11. Wire interaction subscriptions for rendered notification post cards
|
|
12. Update the post.html header comment to document the new parameter
|
|
13. Add LOVE card to `arrApps` array in `index.html` with empty SVG
|
|
14. Add URL mapping for `love` → `post.html?notifications=true` in `index.html`
|