9.2 KiB
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
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
-
npub conversion: Use
NostrTools.nip19.decode()from the already-loadednostr.bundle.jsto handle bech32→hex. If the value doesn't start withnpub, treat it as hex directly. -
Profile card: A new
divProfilesection inserted intodivBodyabovedivPost. It's a read-only card showing: banner, avatar, display name, name, about, website, nip-05, LN address. Styled consistently with existing.divPostItemaesthetic. -
Post box visibility: The existing
divPostelement is shown/hidden based on thepostparameter. When hidden, the keydown listener is not attached. -
Feed subscription logic: The
ndkEventlistener's filter changes based on mode:- Single user mode (
follows=false): Filterevt.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
- Single user mode (
-
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:
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:
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:
<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
if (!showPostBox) {
divPost.style.display = 'none';
} else {
divPost.addEventListener('keydown', handleKeyDown);
}
Step 5: Modify Subscription Logic
Replace the hardcoded currentPubkey subscription with dynamic logic:
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:
// 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:
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:
#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,getPubkeyalready provide everything we needwww/profile.html— Remains as the editable profile pagewww/npub.html— Remains as the QR code display page
Edge Cases
- Invalid npub/hex: If the npub parameter can't be decoded, fall back to logged-in user with a console warning
- No follows: If
follows=truebut the user has no kind 3 event or no followed pubkeys, show an empty feed with a message - 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.
- Post box + other user: When
post=trueand 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 whentargetPubkey !== currentPubkey, or let the user decide via thepostparam.