151 lines
8.9 KiB
Markdown
151 lines
8.9 KiB
Markdown
# FIPS Directory Page Plan
|
|
|
|
## Goal
|
|
Turn [`www/fips-directory.html`](../www/fips-directory.html:1) (currently a copy of the template) into a community FIPS link directory where:
|
|
|
|
- Anyone can **view** FIPS links (no login required).
|
|
- Logged-in users can **add / remove** their own links.
|
|
- There is **no admin** — every person maintains their own **blocklist** (NIP-51 mute list) and decides what to hide from their own view.
|
|
- The site owner publishes their curated FIPS links the same way everyone else does (no hardcoded seed data).
|
|
|
|
## Standards Used
|
|
|
|
### Links — [NIP-B0: Web Bookmarking](../nips/B0.md:1) (`kind:39701`)
|
|
Each FIPS link is a **separate replaceable event**, one per URL:
|
|
|
|
- `kind`: `39701`
|
|
- `d` tag: the FIPS URL (scheme prefix omitted for `https://`, per NIP-B0; for `http://` and `ws://` FIPS URLs the full URL is used)
|
|
- `title` tag: the link name
|
|
- `t` tag: `fips-directory` (so the page can subscribe to just FIPS-directory bookmarks)
|
|
- `content`: markdown description of the link (can be empty)
|
|
- `published_at` tag: unix seconds string
|
|
|
|
**Add a link** = publish a new `kind:39701` event with `d = <url>`.
|
|
**Remove a link** = publish a [NIP-09](../nips/09.md:1) deletion request (`kind:5`) referencing the event id, OR publish an empty/blank replacement (NIP-B0 is replaceable by `d`, so republishing with empty content effectively removes it). We'll use NIP-09 deletion for a clean removal.
|
|
|
|
### Blocklist — [NIP-51: Lists](../nips/51.md:1) (`kind:10000` mute list)
|
|
The standard Nostr mute list. Each user publishes one replaceable event:
|
|
|
|
- `kind`: `10000`
|
|
- `p` tags: pubkeys the user wants hidden from their own view
|
|
- `content`: optionally NIP-44-encrypted private items (we only use public `p` tags)
|
|
|
|
**Block a publisher** = re-publish `kind:10000` with their `p` tag added.
|
|
**Unblock** = re-publish without that `p` tag.
|
|
|
|
> **Sovereign moderation:** blocking only affects the blocking user's own view. It does not delete or hide content for anyone else. This is exactly how every NIP-51-compatible Nostr client already works.
|
|
|
|
## Page Behavior
|
|
|
|
### Public (not logged in)
|
|
- Subscribe to all `kind:39701` events with `#t = ['fips-directory']`.
|
|
- Render every publisher's links as cards grouped by publisher.
|
|
- Show a "Sign in to add your links" prompt.
|
|
- No blocklist controls (no identity to attach a blocklist to).
|
|
|
|
### Logged in
|
|
- Load the user's own `kind:10000` mute list and filter blocked pubkeys out of the rendered list.
|
|
- Show an **Add Link** form (name, URL, description).
|
|
- Show **remove** buttons on the user's own links (publishes a NIP-09 deletion).
|
|
- Show a **block / unblock** button on each publisher's section (for publishers other than yourself).
|
|
- Publishing uses [`publishEvent()`](../www/js/init-ndk.mjs:653) from `init-ndk.mjs` (auto-signs via the worker).
|
|
|
|
### Auth mode
|
|
- `authMode = 'optional'` (matches [`www/app-stacks.html`](../www/app-stacks.html:235)) — public load, login on demand via the sidenav logout/login button or the "Sign in" prompt.
|
|
|
|
## UI Layout (inside `#divBody`)
|
|
|
|
```
|
|
┌─────────────────────────────────────────────┐
|
|
│ FIPS DIRECTORY │ (header text)
|
|
├─────────────────────────────────────────────┤
|
|
│ [Sign in to add your links] (if anon) │
|
|
│ │
|
|
│ ┌─ Add Link form ─────────────────────────┐ │ (only if logged in)
|
|
│ │ Name / URL / Description │ │
|
|
│ │ [Add Link] │ │
|
|
│ └─────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─ Publisher: laantungir ─────────────────┐ │
|
|
│ │ [block] (if logged in & not you) │ │
|
|
│ │ • My Relay — ws://....fips/relay/ │ │
|
|
│ │ [remove] (if yours) │ │
|
|
│ │ • My Client — http://....fips/client │ │
|
|
│ └─────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─ Publisher: someone-else ───────────────┐ │
|
|
│ │ [block] [unblock] │ │
|
|
│ │ • Their Thing — http://....fips/thing │ │
|
|
│ └─────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────┘
|
|
```
|
|
|
|
- Cards use the existing `client.css` variables (`--border-color`, `--border-radius`, `--color`, `--muted-color`, `--font-family`, etc.) — same inline-style pattern as [`www/app-stacks.html`](../www/app-stacks.html:564).
|
|
- Links open in a new tab (`target="_blank" rel="noopener"`).
|
|
- FIPS URLs are clickable as-is (the server is FIPS-enabled, per the user).
|
|
|
|
## Data Structures (in-page state)
|
|
|
|
```js
|
|
// Map: eventId -> { id, pubkey, url, title, description, createdAt }
|
|
let bookmarks = new Map();
|
|
|
|
// Set of blocked pubkeys (from current user's kind 10000)
|
|
let blocklist = new Set();
|
|
|
|
// The current user's kind 10000 event id (for re-publishing)
|
|
let myMuteListEventId = null;
|
|
|
|
// Load flags
|
|
let bookmarksLoaded = false;
|
|
let blocklistLoaded = false;
|
|
```
|
|
|
|
## Implementation Steps
|
|
|
|
1. **Set page title & header text** — change `<title>TEMPLATE</title>` to `FIPS DIRECTORY` and set `.divHeaderText` to "FIPS DIRECTORY".
|
|
2. **Set `authMode = 'optional'`** as the default (like app-stacks.html).
|
|
3. **Add state variables** — `bookmarks` (Map), `blocklist` (Set), `myMuteListEventId`, load flags.
|
|
4. **Add `esc()` helper** — prevent XSS from relay content (same as app-stacks.html).
|
|
5. **Add subscription + listener for `kind:39701` with `#t=['fips-directory']`** — `subscribeFipsBookmarks()` + `initBookmarkListener()`. Parse each event into the bookmarks Map, keyed by event id. Re-render on each event / EOSE.
|
|
6. **Add subscription + listener for `kind:10000` (mute list)** — `subscribeMuteLists()` + `initMuteListListener()`. Only the current user's mute list matters for filtering; subscribe broadly, pick out the logged-in user's. Store blocked pubkeys in the `blocklist` Set.
|
|
7. **Add `renderDirectory()`** — builds the directory HTML (add-link form if logged in, publisher sections grouped by pubkey, link cards with block/remove buttons), writes to `#divBody`. Filters out blocked pubkeys.
|
|
8. **Add `doAddLink()`** — reads the form, publishes a `kind:39701` event with `d=<url>`, `title=<name>`, `t=fips-directory`, `content=<description>`.
|
|
9. **Add `doRemoveLink()`** — publishes a `kind:5` (NIP-09 deletion) event referencing the bookmark's event id, then removes it from the local Map and re-renders.
|
|
10. **Add `doToggleBlock()`** — adds/removes a `p` tag in the user's `kind:10000` mute list, re-publishes, updates the `blocklist` Set, re-renders.
|
|
11. **Wire up subscriptions in `main()`** — after `initializeAuthenticatedPageFeatures()`, call the subscribe/listener init functions (bookmarks subscription runs for both public and logged-in; mute list subscription only matters when logged in but can run always).
|
|
12. **Footer UX note** — "Public mode / Sign in from side menu to add your links" when anonymous.
|
|
|
|
## Architecture Diagram
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
A[User loads fips-directory.html] --> B[authMode = optional]
|
|
B --> C{Logged in?}
|
|
C -- no --> D[Show links + Sign in prompt]
|
|
C -- yes --> E[Show links + Add/Remove form + Block controls]
|
|
D --> F[Subscribe kind 39701 t:fips-directory]
|
|
E --> F
|
|
E --> G[Subscribe kind 10000 mute list]
|
|
F --> H[Render directory cards grouped by pubkey]
|
|
G --> H
|
|
H --> I[User clicks link -> opens FIPS URL]
|
|
E --> J[Add -> publishEvent kind 39701]
|
|
E --> K[Remove -> publishEvent kind 5 NIP-09 deletion]
|
|
E --> L[Block/Unblock -> publishEvent kind 10000]
|
|
J --> H
|
|
K --> H
|
|
L --> H
|
|
```
|
|
|
|
## NIP References
|
|
- [NIP-B0: Web Bookmarking](../nips/B0.md:1) — `kind:39701` for links
|
|
- [NIP-51: Lists](../nips/51.md:1) — `kind:10000` mute list for blocklist
|
|
- [NIP-09: Event Deletion Request](../nips/09.md:1) — `kind:5` for removing a link
|
|
|
|
## Notes
|
|
- No new JS modules needed — everything is inline in the HTML, matching the app-stacks.html pattern.
|
|
- No CSS file changes — uses existing `client.css` variables via inline styles.
|
|
- The `subscribe()` / `publishEvent()` / `getPubkey()` APIs from [`www/js/init-ndk.mjs`](../www/js/init-ndk.mjs:1) are already imported by the template.
|
|
- Fully standards-based: other Nostr clients that support NIP-B0 bookmarks and NIP-51 mute lists will interoperate with the data we publish.
|