# Post Composer Component — Plan

## Overview

Create a **reusable post-composer module** (`www/js/post-composer.mjs`) that encapsulates:

1. **File upload** — drag-and-drop onto the compose area + click-to-upload icon
2. **Live preview** — renders the post content as it will appear (images, `nostr:` entities, links)
3. **@ mention autocomplete** — type `@name` to search followed users and insert their `nostr:npub1…` reference
4. **Reusability** — the same module can be mounted inside `post.html`, reply dialogs, or any future compose context

The composer uploads files to **all configured blossom servers** (via existing `uploadToAllServers`) but inserts only the **default server URL** into the post text (via existing `getDefaultBlossomServer` / `getBlobUrl`).

---

## Architecture

```mermaid
graph TD
    A[post-composer.mjs] -->|imports| B[blossom-api.mjs]
    A -->|imports| C[blossom-ui.mjs]
    A -->|imports| D[utilities.mjs - htmlFormatText]
    A -->|imports| E[post-interactions.mjs - hydrateNostrEntities]
    F[post.html] -->|calls| A
    G[future reply dialog] -->|calls| A
    A -->|receives| H[contact list - followed pubkeys + profiles]
```

### Module API — `post-composer.mjs`

```js
/**
 * Mount a post composer onto an existing contenteditable div.
 *
 * @param {HTMLElement} hostEl — the contenteditable element, e.g. divPost
 * @param {Object} options
 * @param {string}   options.currentPubkey — logged-in user hex pubkey
 * @param {Array}    options.followedProfiles — [{pubkey, name, display_name, picture}, ...]
 * @param {Function} options.onSubmit — called with final plain-text content string
 * @param {Function} options.fetchCachedProfile — profile lookup fn
 * @param {boolean}  [options.showUploadIcon=true]
 * @param {boolean}  [options.showPreview=true]
 * @returns {{ destroy: Function, refresh: Function }}
 */
export function mountComposer(hostEl, options) { ... }
```

The module does **not** own the `<div contenteditable>` element — it wraps around it, adding:
- A **wrapper div** around the host for positioning context
- An **upload icon** (bottom-right of the host)
- A **preview div** (below the host, inside the same wrapper)
- An **@ mention dropdown** (positioned inline near the cursor)
- Drag-and-drop event listeners on the host
- A hidden `<input type="file">` for click-to-upload

---

## Detailed Design

### 1. File Upload

**Drag & Drop on divPost:**
- Listen for `dragover`, `dragleave`, `drop` on the host element
- On `drop`: extract files, show a visual uploading indicator, call `uploadToAllServers(file)` from `blossom-api.mjs`
- On success: construct the URL using `getBlobUrl(sha256, ext)` from `blossom-api.mjs` — this uses the **default** (first healthy upload) server
- Insert the URL at the cursor position in the contenteditable div
  - For images: insert the raw URL on its own line (the preview will render it as `<img>`)
  - For other files: insert the URL as a link

**Upload Icon (bottom-right):**
- A small clickable icon (SVG paperclip or upload arrow) positioned absolutely in the bottom-right corner of the host element
- On click: trigger a hidden `<input type="file" accept="image/*,video/*,audio/*">` 
- Same upload flow as drag-and-drop

**Upload Progress:**
- While uploading, show a small spinner or progress text overlay on the host element
- Disable the submit (Enter key) while upload is in progress

### 2. Live Preview

**Trigger:** The preview div appears once the user starts typing (first `input` event with non-empty content).

**Rendering:**
- On each `input` event (debounced ~300ms), take `hostEl.innerText`, run it through `htmlFormatText()` from `utilities.mjs`
- Set the preview div's `innerHTML` to the result
- Call `hydrateNostrEntities(previewEl)` from `post-interactions.mjs` to resolve embedded notes/profiles
- The preview div uses the same CSS classes as `.divPostContent` so it looks identical to rendered posts

**Preview div placement:**
- Sits below the compose area inside the same wrapper
- Has a subtle top border or label like "Preview" in muted text
- Hidden when compose area is empty

### 3. @ Mention Autocomplete

**Trigger:** When the user types `@` followed by at least 1 character, search the `followedProfiles` array.

**Search logic:**
- Filter profiles where `name`, `display_name`, or `nip05` contains the typed substring (case-insensitive)
- Show up to 8 results in a dropdown positioned near the cursor

**Dropdown UI:**
- Absolutely positioned dropdown with avatar thumbnail, display name, and @name
- Keyboard navigation: arrow keys to select, Enter/Tab to confirm, Escape to dismiss
- Click to select

**Insertion:**
- On selection, replace the `@query` text with `nostr:npub1…` (the bech32-encoded npub of the selected user)
- The preview will then render this as a clickable mention link via `htmlFormatText`

**Profile data source:**
- `post.html` already fetches the contact list (kind 3) and has `followedPubkeys`
- The composer receives pre-fetched profile data via `options.followedProfiles`
- Profiles are lazily enriched: if a followed pubkey has no cached profile yet, the dropdown shows the truncated npub

### 4. Integration with post.html

**Current flow in `post.html`:**
1. `divPost` is a `contenteditable` div
2. `handleKeyDown` listens for Enter (without Shift) to call `handlePost()`
3. `handlePost()` reads `divPost.innerText`, publishes kind 1 event

**New flow:**
1. After `divPost` is set up and `interactions` is initialized, call `mountComposer(divPost, { ... })`
2. The composer adds upload, preview, and mention features
3. `handlePost()` remains the same — it reads `divPost.innerText` which now may contain `nostr:npub1…` references and image URLs
4. The composer's `onSubmit` callback is optional — `post.html` can continue using its own Enter key handler

**DOM changes to post.html:**
- Wrap `divPost` in a new `div#divPostWrapper` (or the composer creates this wrapper dynamically)
- The preview div and upload icon are children of this wrapper
- Minimal CSS additions for the wrapper, upload icon positioning, preview styling, and mention dropdown

---

## File Changes Summary

| File | Change |
|------|--------|
| `www/js/post-composer.mjs` | **NEW** — the reusable composer module |
| `www/css/post-composer.css` | **NEW** — styles for upload icon, preview, mention dropdown, drag overlay |
| `www/post.html` | **MODIFY** — import and mount the composer; add CSS link; minor DOM adjustments |
| `www/js/utilities.mjs` | No changes needed — `htmlFormatText` already handles images and nostr entities |
| `www/js/blossom-api.mjs` | No changes needed — `uploadToAllServers`, `getBlobUrl`, `getDefaultBlossomServer` already exist |
| `www/js/post-interactions.mjs` | No changes needed — `hydrateNostrEntities` is already exported |

---

## Implementation Steps

### Step 1: Create `www/css/post-composer.css`
- `.post-composer-wrapper` — relative positioning container
- `.post-composer-upload-icon` — absolute bottom-right, clickable, hover state
- `.post-composer-drag-overlay` — full overlay on dragover with dashed border and upload icon
- `.post-composer-preview` — preview area below compose, uses `.divPostContent` base styles
- `.post-composer-preview-label` — small muted "Preview" label
- `.post-composer-mention-dropdown` — absolutely positioned autocomplete list
- `.post-composer-mention-item` — individual mention result with avatar, name
- `.post-composer-mention-item.active` — highlighted/selected item
- `.post-composer-uploading` — upload-in-progress indicator

### Step 2: Create `www/js/post-composer.mjs`
- Export `mountComposer(hostEl, options)` function
- Implement drag-and-drop upload with visual feedback
- Implement click-to-upload via hidden file input
- Implement debounced live preview using `htmlFormatText` + `hydrateNostrEntities`
- Implement @ mention detection, search, dropdown rendering, and npub insertion
- Return `{ destroy, refresh }` for cleanup and profile list updates

### Step 3: Integrate into `www/post.html`
- Add `<link rel="stylesheet" href="./css/post-composer.css" />` in `<head>`
- Import `mountComposer` from `./js/post-composer.mjs`
- After `interactions` is initialized and contact list is loaded, build `followedProfiles` array
- Call `mountComposer(divPost, { currentPubkey, followedProfiles, fetchCachedProfile })`
- Update `followedProfiles` when new profiles arrive (via `composer.refresh({ followedProfiles })`)

### Step 4: Test all features
- Drag an image file onto divPost — verify upload to all blossom servers, URL inserted with default server
- Click upload icon — verify file picker opens, same upload flow
- Type text with an image URL — verify preview shows the image
- Type `nostr:npub1...` — verify preview shows mention link
- Type `@jack` — verify dropdown appears with matching follows, selection inserts npub
- Press Enter — verify post publishes with correct content including URLs and npub references

---

## Mermaid: Composer Interaction Flow

```mermaid
sequenceDiagram
    participant U as User
    participant C as Composer
    participant B as Blossom API
    participant P as Preview

    U->>C: Drops image file on divPost
    C->>C: Show drag overlay + uploading indicator
    C->>B: uploadToAllServers - file
    B-->>C: blobDescriptor + sha256
    C->>C: getBlobUrl - sha256, ext - default server URL
    C->>C: Insert URL at cursor in divPost
    C->>P: Debounced preview update
    P->>P: htmlFormatText renders img tag
    
    U->>C: Types @jac
    C->>C: Detect @ trigger, search followedProfiles
    C->>C: Show mention dropdown with matches
    U->>C: Selects jack from dropdown
    C->>C: Replace @jac with nostr:npub1abc...
    C->>P: Debounced preview update
    P->>P: htmlFormatText renders mention link
    
    U->>C: Presses Enter
    C->>C: onSubmit callback or native handler
```

---

## Mermaid: Module Dependency Graph

```mermaid
graph LR
    PC[post-composer.mjs] --> BA[blossom-api.mjs]
    PC --> BU[blossom-ui.mjs]
    PC --> UT[utilities.mjs]
    PC --> PI[post-interactions.mjs]
    BA --> BU
    PH[post.html] --> PC
    PH --> PI
    PH --> BA
    PH --> BU
    PH --> UT
```

---

## Key Design Decisions

1. **Module, not Web Component** — Using a plain ES module with `mountComposer()` rather than a custom element, because the existing codebase uses ES modules throughout and the contenteditable div is already in the HTML. This avoids Shadow DOM complexity and keeps CSS consistent.

2. **Default server for URLs** — `getBlobUrl()` in `blossom-api.mjs` already implements BUD-03 logic (first healthy upload server). We use this for the URL inserted into the post, while `uploadToAllServers()` handles redundant storage.

3. **Preview reuses existing rendering** — By calling `htmlFormatText()` + `hydrateNostrEntities()`, the preview is guaranteed to match how posts look in the feed. No duplicate rendering logic.

4. **@ mentions use npub, not display name** — The inserted text is `nostr:npub1…` which is the Nostr standard. The preview and feed rendering will resolve this to a clickable name via `htmlFormatText`.

5. **Contact list as input** — The composer receives the followed profiles list from the host page rather than fetching it independently. This avoids duplicate subscriptions and lets the host page control when/how profiles are loaded.
