# Active Context

## Current Work Focus
PDF Embed Gutenberg block — all core implementation complete, blank-content bug fixed.

## Feature Summary
A `/pdf` Gutenberg block lets editors browse WPFD files, select a PDF, and embed it inline using PDF.js. The rendered shortcode `[wpfdpdf id="X" catid="Y" name="title" embed="1"]` creates an iframe pointing at `pdf-embed.php`, which renders the PDF.js viewer, loading the file via a dedicated AJAX stream endpoint.

## Recent Changes (this session)

### Live Preview in Block Editor (ServerSideRender)
Added a live `do_shortcode` preview inside the Gutenberg block editor after a PDF is selected.

**`app/functions.php`** — three additions:
1. `'wp-server-side-render'` added to the `wpfd-blocks` script dependency array.
2. `wpfd_pdf_block_render_callback($attributes)` — calls `do_shortcode($attributes['shortCode'])` and enqueues `wpfd-pdf-embed.css`; returns the rendered embed HTML.
3. `wpfd_register_pdf_block()` hooked on `init` — calls `register_block_type('wpfd/wpfd-pdf', [...])` with the render callback and all block attributes (`isPreview`, `fileId`, `catId`, `fileName`, `embed`, `target`, `shortCode`). This exposes the REST endpoint `/wp/v2/block-renderer/wpfd/wpfd-pdf` that `ServerSideRender` calls.

**`app/admin/assets/blocks/pdf/wpfd-pdf.jsx`** — two additions:
1. `const ServerSideRender = wp.serverSideRender;` declared at the top of the IIFE.
2. In the "file selected" render state, added after the `wpfd-pdf-block` wrapper `</div>` and before the modal:
   ```jsx
   {shortcode && ServerSideRender &&
     <ServerSideRender block="wpfd/wpfd-pdf" attributes={attributes} />
   }
   ```
   This triggers a REST call to the render callback whenever `shortcode` is non-empty, displaying the live PDF embed iframe directly in the editor.

**Rebuild** — `npm run build` completed without errors (wpfd-blocks.js = 130 kB).

### Bug Fixes Applied
1. **`app/admin/assets/blocks/pdf/wpfd-pdf.jsx`** — `setSelectedFile()` now uses
   `this.state.categorySelectedId || file.term_id` as the `catid` in the shortcode.
   Previously `file.term_id` could be `undefined` if the files-API response didn't include
   it, causing `catid="undefined"` → `(int) 'undefined' = 0` → `wp_die(400)` in the stream
   handler → blank PDF viewer.

2. **`app/site/templates/pdf-embed.php`** — Complete rewrite to match the working
   `wp-media-folder` reference template structure exactly. Key corrections made:
   - **Editor param toolbars moved**: `#editorFreeTextParamsToolbar`, `#editorInkParamsToolbar`,
     `#editorStampParamsToolbar` are now **direct children of `#mainContainer`** (after findbar,
     before secondaryToolbar) — not inside `#toolbarContainer`. PDF.js `getViewerConfiguration()`
     positions them via `closest('#mainContainer')`, so placement must be correct.
   - **Slider inputs fixed**: `editorFreeTextFontSize`, `editorInkThickness`, `editorInkOpacity`
     changed from `<select>` to `<input type="range">` with correct `min`/`max`/`step` attributes.
     PDF.js `AnnotationEditorParams` constructor reads `.min`/`.max`/`.step` on these elements;
     `<select>` doesn't have these properties → crash.
   - **`altTextDialog` structure fixed**: now uses the nested `#altTextContainer` →
     `#overallDescription` → `#addDescription` → `#markAsDecorative` → `#buttons` hierarchy
     that `AltTextManager` constructor walks to attach event listeners (the original flat
     structure caused `TypeError: Cannot read properties of null (reading 'addEventListener')`).
   - **`presentationMode`** moved into `#toolbarViewerRight` (was hidden in secondaryToolbar).
   - **`viewBookmark`** changed from `<button>` to `<a href="#">` as PDF.js expects an anchor.
   - **Spread buttons** (`spreadNone`, `spreadOdd`, `spreadEven`) made visible (removed `hidden`).
   - **`editorModeButtons`** changed from `class="hidden"` to `class="splitToolbarButton toggled"`.
   - **`openFile`** changed from `hidden` to `hiddenLargeView`.
   - **`secondaryOpenFile`** changed from `hidden` to `visibleLargeView`.
   - Retained WPFD-specific PHP header (URL sanitisation), asset paths (`app/site/assets/`),
     and the `webviewerloaded` JS fallback for reliable PDF loading.

3. **`app/site/init.php` — `wpfd_pdf_embed_stream()`** — Now handles remote/cloud files:
   if `_wpfd_file_metadata` has a `remote_url` but no local `file` key, the handler
   redirects the browser to the remote URL (so PDF.js can fetch directly) instead of
   returning a 404.  Also added `Access-Control-Allow-Origin: <site_url>` header to
   the local-file response.

4. **Rebuild** — `npm run build` completed without errors (wpfd-blocks.js = 130 kB).

## Files Involved

| File | Role |
|------|------|
| `app/admin/assets/blocks/pdf/block.json` | Block registration metadata |
| `app/admin/assets/blocks/pdf/wpfd-pdf.jsx` | Gutenberg edit/save component |
| `app/admin/assets/js/wpfd-blocks.js` | Compiled output (webpack) |
| `app/site/templates/pdf-embed.php` | Standalone PDF.js viewer iframe template |
| `app/site/assets/js/pdf-embed/` | pdf.js, pdf.worker.js, pdf.sandbox.js, viewer.js |
| `app/site/assets/css/pdf-embed/` | viewer.css + images/ + locale/ |
| `app/site/assets/css/wpfd-pdf-embed.css` | Wrapper/iframe CSS for the parent page |
| `app/site/helpers/WpfdHelperShortcodes.php` | `pdfEmbedShortcode()` method |
| `app/site/init.php` | `wpfd_pdf_embed_stream()` AJAX handler |

## Architecture: PDF Embed Data Flow

```
Gutenberg block (edit)
  └─ File picker modal (WP AJAX: categories.listCats, files view)
       └─ "Insert this file" → shortcode saved to post content
            [wpfdpdf id="X" catid="Y" name="Title" embed="1"]

Frontend page render
  └─ WpfdHelperShortcodes::pdfEmbedShortcode()
       └─ builds $streamUrl = admin-ajax.php?action=wpfd_pdf_embed_stream&wpfd_file_id=X&...
       └─ builds $viewerUrl = pdf-embed.php?file=ENCODED_STREAM_URL&plugins_url=ENCODED_PLUGIN_URL
       └─ outputs <iframe src="$viewerUrl" width="100%" height="800">

pdf-embed.php (standalone, no WP context)
  └─ loads pdf.js, viewer.css, viewer.js
  └─ sets wpfd_pdf_embed_obj.{workerSrc, sandboxBundleSrc, imageResourcesPath, errorLoadingPdf}
  └─ injects PHP-side $pdf_file_url as JS fallback via webviewerloaded listener
  └─ viewer.js reads params.get('file') → stream URL → fetches PDF binary

wpfd_pdf_embed_stream() [wp_ajax_nopriv + wp_ajax]
  └─ reads wpfd_file_id, wpfd_category_id
  └─ loads WpfdBase, reads _wpfd_file_metadata
  └─ local file  → streams binary with Content-Type: application/pdf
  └─ remote file → wp_redirect($remote_url)
```

## Known Remaining Considerations
- Cloud files (OneDrive, Google Drive, Dropbox) are redirected to the remote URL; the remote
  URL might expire for OAuth-based cloud providers. This is acceptable behaviour for now.
- The `validateFileURL` function in viewer.js allows same-origin http/https URLs; the AJAX
  stream endpoint is always same-origin, so validation passes.

## Next Steps
- Manual test: insert PDF block → select local PDF → verify render on frontend.
- Optional: add `height` / `width` block attributes to the Gutenberg inspector panel.
- Optional: restrict `wpfd_pdf_embed_stream` to logged-in users if private-file access
  control is needed (currently open to `wp_ajax_nopriv`).
