# NocoBase PDF Previewer Plugin - AI Coding Guide ## Plugin Architecture This is a **NocoBase plugin** with client/server architecture. The client side provides a feature-rich PDF viewer; the server side is minimal (just plugin registration). **Entry Points:** - Client: `src/client/index.tsx` - registers PDF file type with `attachmentFileTypes.add()` - Server: `src/server/index.ts` - empty Plugin class for future extensions - Package exports follow NocoBase convention: `./client` and `./server` paths ## Build System (Critical) **Build Command:** `npm run build` executes 3 steps sequentially: 1. `rimraf dist` - clean old build 2. `node scripts/build-esbuild.js` - compile server code to CommonJS 3. `rollup -c` - bundle client code to AMD format 4. `node scripts/bundle-deps.js` - verify client bundle **Output Format Requirements:** - **Client MUST be AMD format** - NocoBase uses AMD/RequireJS module system - **Server is CommonJS** - Node.js standard - Rollup config (`rollup.config.js`) outputs `format: 'amd'` for client **External vs Bundled Dependencies:** - External (provided by NocoBase runtime): `@nocobase/client`, `react`, `react-dom`, `antd`, `@ant-design/icons` - Bundled (included in plugin): `pdfjs-dist`, `react-pdf`, `file-saver` - Always check `external` array in `rollup.config.js` when adding dependencies ## PDF.js Worker Configuration (CRITICAL) **The Problem:** NocoBase's AMD/RequireJS environment intercepts worker URLs and adds `.js` extension, causing 404 errors. **The Solution (DO NOT CHANGE):** Blob URL approach in `PDFPreviewer.tsx`: ```tsx useEffect(() => { const workerUrl = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`; fetch(workerUrl) .then(response => response.blob()) .then(blob => { const blobUrl = URL.createObjectURL(blob); pdfjs.GlobalWorkerOptions.workerSrc = blobUrl; // Blob URL bypasses AMD }); }, []); ``` **Why this matters:** Blob URLs (`blob://...`) are not intercepted by AMD. Direct CDN URLs fail because AMD appends `.js` to them. ## Internationalization Pattern **Inline Registration (NOT separate JSON files):** Locales are registered in `src/client/index.tsx` inside the `afterAdd()` hook, not `load()`: ```tsx async afterAdd() { this.app.i18n.addResources('en-US', NAMESPACE, { /* translations */ }); this.app.i18n.addResources('zh-CN', NAMESPACE, { /* translations */ }); } ``` **Usage in components:** ```tsx import { useT } from './locale'; const t = useT(); // Returns translation function t('Page'); // Translates using 'file-previewer-pdfjs' namespace ``` **Namespace constant:** `NAMESPACE = 'file-previewer-pdfjs'` in `src/client/locale.tsx` ## Component Integration with NocoBase **File Previewer Props:** ```tsx interface PDFPreviewerProps { index: number; // Current file index in list list: FileType[]; // Array of file objects onSwitchIndex: (index: number | null) => void; // Close with null } ``` **File Object Structure:** ```tsx interface FileType { url: string; // File URL (absolute or relative) title: string; // Display name extname: string; // e.g., ".pdf" mimetype?: string; // e.g., "application/pdf" } ``` **Registration Pattern:** Use `attachmentFileTypes.add()` with `match()` function and `Previewer` component. ## UI Component Patterns **Modal-based Viewer:** Uses Antd Modal with custom toolbar for controls **Toolbar Features:** Page navigation, zoom (8 levels: 50%-300%), rotation (90° increments), fit-to-width/page, search, download **Search Implementation:** Uses text layer from react-pdf (`renderTextLayer={true}`), highlights matches with `` elements (yellow for all, orange for current), manages state via `searchText`, `searchMatches`, `currentMatch`. **Styling Convention:** - Dark viewer background: `#525659` - Toolbar background: `#f5f5f5` - Inline styles preferred over CSS files ## File Structure Conventions ``` src/ ├── client/ │ ├── index.tsx # Plugin registration + i18n │ ├── PDFPreviewer.tsx # Main viewer component │ └── locale.tsx # i18n utilities ├── server/ │ └── index.ts # Server plugin (usually empty) └── index.ts # Root export (optional) ``` **DO NOT create:** Separate locale JSON files, CSS modules, or additional directories unless necessary. ## Testing and Packaging **Local testing:** `npm run build && npm pack` creates `.tgz` file **Installation:** NocoBase installs from npm or local `.tgz` **Package contents:** Only `dist/`, `package.json`, `README.md` (see `.npmignore`) ## Common Pitfalls 1. **Never change worker loading to direct CDN URL** - will break in AMD environment 2. **Never change client output format from AMD** - NocoBase requires AMD modules 3. **Don't externalize bundled dependencies** (pdfjs-dist, react-pdf, file-saver) - they must be bundled 4. **Don't add locale JSON files** - use inline registration in `afterAdd()` hook 5. **Always handle null index** - component receives null when closed ## Adding New Features **New toolbar button:** Add state variable, button to toolbar JSX, handler function, and translations to both locales in `afterAdd()`. **New PDF.js feature:** Import from `react-pdf` or `pdfjs-dist` (already bundled), check if worker changes needed (usually no), test thoroughly in AMD environment. **New external dependency:** Add to `peerDependencies` in `package.json`, add to `external` array in `rollup.config.js`, add to `globals` mapping if used in client code.