263 lines
9.2 KiB
Markdown
263 lines
9.2 KiB
Markdown
# Plan: post.html URL-Driven Optionality
|
|
|
|
## Overview
|
|
|
|
Transform `post.html` into a generic, URL-parameter-driven page that can serve as:
|
|
- **My Posts** page (current default behavior)
|
|
- **User Profile** page (any user's profile + their posts)
|
|
- **User Posts** page (any user's posts without profile header)
|
|
- **Home Feed** page (posts from users you follow)
|
|
|
|
All controlled via URL query parameters, with full combinability.
|
|
|
|
---
|
|
|
|
## URL Parameters
|
|
|
|
| Parameter | Values | Default | Description |
|
|
|-----------|--------|---------|-------------|
|
|
| `npub` | hex pubkey or bech32 npub | logged-in user | Whose posts to display |
|
|
| `profile` | `true` / `false` | `false` | Show read-only profile card above posts |
|
|
| `post` | `true` / `false` | `true` | Show the post composition box |
|
|
| `follows` | `true` / `false` | `false` | Show posts from the target user's follows instead of just their posts |
|
|
|
|
### Example URLs
|
|
|
|
```
|
|
post.html → Own posts + post box (current behavior)
|
|
post.html?post=false → Own posts, no post box
|
|
post.html?npub=npub1rmz9... → That user's posts, with post box
|
|
post.html?npub=abc123hex&profile=true → Profile card + that user's posts
|
|
post.html?npub=npub1rmz9...&profile=true&post=false → Read-only profile page for a user
|
|
post.html?follows=true → Home feed (posts from people you follow)
|
|
post.html?follows=true&post=true → Home feed with post box
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
### Parameter Parsing Flow
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
A[Page Load] --> B[Parse URL params]
|
|
B --> C{npub param?}
|
|
C -->|Yes| D[Convert to hex if bech32]
|
|
C -->|No| E[Use logged-in user pubkey]
|
|
D --> F[targetPubkey = parsed hex]
|
|
E --> F
|
|
F --> G{profile=true?}
|
|
G -->|Yes| H[Fetch kind 0 for targetPubkey]
|
|
G -->|No| I[Skip profile section]
|
|
H --> J[Render read-only profile card]
|
|
I --> K{post param}
|
|
J --> K
|
|
K -->|true / default| L[Show divPost contenteditable]
|
|
K -->|false| M[Hide divPost]
|
|
L --> N{follows=true?}
|
|
M --> N
|
|
N -->|Yes| O[Fetch kind 3 contact list for targetPubkey]
|
|
N -->|No| P[Subscribe to kind 1 for targetPubkey only]
|
|
O --> Q[Subscribe to kind 1 for all followed pubkeys]
|
|
P --> R[Render feed]
|
|
Q --> R
|
|
```
|
|
|
|
### Key Design Decisions
|
|
|
|
1. **npub conversion**: Use `NostrTools.nip19.decode()` from the already-loaded `nostr.bundle.js` to handle bech32→hex. If the value doesn't start with `npub`, treat it as hex directly.
|
|
|
|
2. **Profile card**: A new `divProfile` section inserted into `divBody` above `divPost`. It's a read-only card showing: banner, avatar, display name, name, about, website, nip-05, LN address. Styled consistently with existing `.divPostItem` aesthetic.
|
|
|
|
3. **Post box visibility**: The existing `divPost` element is shown/hidden based on the `post` parameter. When hidden, the keydown listener is not attached.
|
|
|
|
4. **Feed subscription logic**: The `ndkEvent` listener's filter changes based on mode:
|
|
- **Single user mode** (`follows=false`): Filter `evt.pubkey === targetPubkey` (current behavior, just with configurable target)
|
|
- **Follows mode** (`follows=true`): Fetch kind 3 for targetPubkey, extract followed pubkeys, subscribe to kind 1 for all of them, filter events from any of those pubkeys
|
|
|
|
5. **Page title**: Dynamically set based on mode — e.g., "POST", "FEED", or the user's display name.
|
|
|
|
---
|
|
|
|
## Implementation Steps
|
|
|
|
### Step 1: Add URL Parameter Parsing
|
|
|
|
At the top of the `main()` function in `post.html`, parse URL parameters:
|
|
|
|
```javascript
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const npubParam = urlParams.get('npub'); // hex or bech32
|
|
const profileParam = urlParams.get('profile'); // 'true' or 'false'
|
|
const postParam = urlParams.get('post'); // 'true' or 'false'
|
|
const followsParam = urlParams.get('follows'); // 'true' or 'false'
|
|
|
|
const showProfile = profileParam === 'true';
|
|
const showPostBox = postParam !== 'false'; // default true
|
|
const showFollows = followsParam === 'true';
|
|
```
|
|
|
|
### Step 2: Add npub→hex Conversion
|
|
|
|
After NDK initialization, resolve the target pubkey:
|
|
|
|
```javascript
|
|
let targetPubkey = currentPubkey; // default: logged-in user
|
|
|
|
if (npubParam) {
|
|
if (npubParam.startsWith('npub')) {
|
|
// Wait for NostrTools to be available from nostr.bundle.js
|
|
const decoded = window.NostrTools.nip19.decode(npubParam);
|
|
targetPubkey = decoded.data;
|
|
} else {
|
|
targetPubkey = npubParam; // assume hex
|
|
}
|
|
}
|
|
```
|
|
|
|
### Step 3: Add Profile Card Section
|
|
|
|
Add a new `divProfile` element to the HTML body, positioned above `divPost` in `divBody`:
|
|
|
|
```html
|
|
<div id="divProfile" style="display: none;"></div>
|
|
<div id="divPost" contenteditable="true"></div>
|
|
```
|
|
|
|
When `showProfile` is true, fetch kind 0 for `targetPubkey` and render a read-only card:
|
|
|
|
- Banner image (full width)
|
|
- Avatar (circular/rounded)
|
|
- Display name / name
|
|
- About text
|
|
- Website link
|
|
- NIP-05
|
|
- LN address
|
|
|
|
Reuse the existing `fetchProfile()` from `post-interactions.mjs` for the kind 0 fetch, or use `fetchEventsFromAllRelays` directly for the full event data (since `fetchProfile` only caches a subset of fields).
|
|
|
|
### Step 4: Conditionally Show/Hide Post Box
|
|
|
|
```javascript
|
|
if (!showPostBox) {
|
|
divPost.style.display = 'none';
|
|
} else {
|
|
divPost.addEventListener('keydown', handleKeyDown);
|
|
}
|
|
```
|
|
|
|
### Step 5: Modify Subscription Logic
|
|
|
|
Replace the hardcoded `currentPubkey` subscription with dynamic logic:
|
|
|
|
```javascript
|
|
if (showFollows) {
|
|
// Fetch kind 3 contact list for targetPubkey
|
|
const contactEvents = await fetchEventsFromAllRelays(
|
|
{ kinds: [3], authors: [targetPubkey], limit: 1 }
|
|
);
|
|
// Extract followed pubkeys from tags
|
|
const followedPubkeys = extractFollowedPubkeys(contactEvents);
|
|
|
|
// Subscribe to posts from all followed users
|
|
subscribe(
|
|
{ kinds: [1], authors: followedPubkeys, limit: 50 },
|
|
{ closeOnEose: false, cacheUsage: 'CACHE_FIRST' }
|
|
);
|
|
} else {
|
|
// Subscribe to posts from targetPubkey only
|
|
subscribe(
|
|
{ kinds: [1], authors: [targetPubkey], limit: 50 },
|
|
{ closeOnEose: false, cacheUsage: 'CACHE_FIRST' }
|
|
);
|
|
}
|
|
```
|
|
|
|
### Step 6: Update Event Listener Filter
|
|
|
|
The `ndkEvent` listener currently checks `evt.pubkey === currentPubkey`. This needs to change:
|
|
|
|
```javascript
|
|
// Build the set of pubkeys we're interested in
|
|
let feedPubkeys = new Set();
|
|
if (showFollows) {
|
|
followedPubkeys.forEach(pk => feedPubkeys.add(pk));
|
|
} else {
|
|
feedPubkeys.add(targetPubkey);
|
|
}
|
|
|
|
// In the event listener:
|
|
if (evt.kind === 1 && feedPubkeys.has(evt.pubkey)) {
|
|
// ... existing post handling logic
|
|
}
|
|
```
|
|
|
|
### Step 7: Update Page Title and Header
|
|
|
|
Dynamically set the header text based on the mode:
|
|
|
|
```javascript
|
|
const headerText = document.querySelector('.divHeaderText');
|
|
if (showFollows) {
|
|
headerText.textContent = 'FEED';
|
|
document.title = 'FEED';
|
|
} else if (npubParam && targetPubkey !== currentPubkey) {
|
|
// Viewing another user - will update with their name when profile loads
|
|
headerText.textContent = 'USER';
|
|
document.title = 'USER';
|
|
} else {
|
|
headerText.textContent = 'POST';
|
|
document.title = 'POST';
|
|
}
|
|
```
|
|
|
|
### Step 8: Add Profile Card CSS
|
|
|
|
Add styles for the read-only profile card in the `<style>` block:
|
|
|
|
```css
|
|
#divProfile {
|
|
width: 60%;
|
|
min-width: 300px;
|
|
max-width: 600px;
|
|
margin-bottom: 20px;
|
|
border: 2px solid var(--primary-color);
|
|
border-radius: 10px;
|
|
background: var(--secondary-color);
|
|
overflow: hidden;
|
|
}
|
|
```
|
|
|
|
### Step 9: Update See More Button for Follows Mode
|
|
|
|
The "See More" button's fetch-older logic needs to use the correct author list (followed pubkeys or target pubkey) when requesting older posts.
|
|
|
|
### Step 10: Handle Post Publishing in Non-Own-User Mode
|
|
|
|
When viewing another user's posts, the post box (if shown) should still publish as the logged-in user. The published post won't appear in the feed unless the logged-in user is the target user. Consider adding a note or handling this gracefully.
|
|
|
|
---
|
|
|
|
## Files Modified
|
|
|
|
| File | Changes |
|
|
|------|---------|
|
|
| `www/post.html` | URL param parsing, conditional rendering, profile card HTML/CSS, dynamic subscriptions, updated event filtering, dynamic page title |
|
|
| `www/js/post-interactions.mjs` | Possibly export `fetchProfile` or add a `fetchFullProfile` that returns all kind 0 fields (not just the cached subset) |
|
|
|
|
## Files NOT Modified
|
|
|
|
- `www/js/init-ndk.mjs` — No changes needed; `subscribe`, `fetchEventsFromAllRelays`, `getPubkey` already provide everything we need
|
|
- `www/profile.html` — Remains as the editable profile page
|
|
- `www/npub.html` — Remains as the QR code display page
|
|
|
|
---
|
|
|
|
## Edge Cases
|
|
|
|
1. **Invalid npub/hex**: If the npub parameter can't be decoded, fall back to logged-in user with a console warning
|
|
2. **No follows**: If `follows=true` but the user has no kind 3 event or no followed pubkeys, show an empty feed with a message
|
|
3. **Large follow lists**: Kind 3 can have hundreds of pubkeys. The subscription should batch or limit to avoid overwhelming relays. Consider subscribing in chunks of 50-100 authors.
|
|
4. **Post box + other user**: When `post=true` and viewing another user, the post box publishes as the logged-in user. The post won't appear in the feed. Could either hide the post box automatically when `targetPubkey !== currentPubkey`, or let the user decide via the `post` param.
|