Spaces:
Running
Running
| /* ===== Clipboard Paste Support ===== */ | |
| window.NAIPaste = { | |
| /** | |
| * Registry of paste targets. | |
| * Each entry: { sel, page, handler } | |
| */ | |
| targets: [], | |
| /** The most recently focused/interacted dropzone target (within current page) */ | |
| _lastActiveTarget: null, | |
| /** | |
| * Register a single-image dropzone as a paste target. | |
| */ | |
| register(dropzoneSelector, pageId, handler) { | |
| const entry = { sel: dropzoneSelector, page: pageId, handler }; | |
| this.targets.push(entry); | |
| const dz = document.querySelector(dropzoneSelector); | |
| if (!dz) return; | |
| // Make focusable | |
| dz.setAttribute('tabindex', '0'); | |
| // Track interaction: click, focus, dragover all mark this as "last active" | |
| const markActive = () => { this._lastActiveTarget = entry; }; | |
| dz.addEventListener('focus', markActive); | |
| dz.addEventListener('click', markActive); | |
| dz.addEventListener('dragover', markActive); | |
| dz.addEventListener('mouseenter', markActive); | |
| // Direct paste when dropzone has focus | |
| dz.addEventListener('paste', (e) => { | |
| const file = this._extractImageFile(e); | |
| if (file) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| handler(file); | |
| } | |
| }); | |
| }, | |
| _extractImageFile(e) { | |
| const items = e.clipboardData?.items; | |
| if (!items) return null; | |
| for (const item of items) { | |
| if (item.type.startsWith('image/')) return item.getAsFile(); | |
| } | |
| return null; | |
| }, | |
| _currentPage() { | |
| return NAIState.currentPage || 'generate'; | |
| }, | |
| /** | |
| * Find paste target priority: | |
| * 1. Last actively interacted dropzone on current page (if its panel is open) | |
| * 2. Last open-panel dropzone on current page (bottom-up) | |
| * 3. Fallback: first target on current page | |
| */ | |
| _findTarget() { | |
| const currentPage = this._currentPage(); | |
| const candidates = this.targets.filter(t => t.page === currentPage); | |
| if (candidates.length === 0) return null; | |
| // 1. Last actively interacted (if on current page and panel is open) | |
| if (this._lastActiveTarget && this._lastActiveTarget.page === currentPage) { | |
| const dz = document.querySelector(this._lastActiveTarget.sel); | |
| if (dz && this._isDropzoneAccessible(dz)) { | |
| return this._lastActiveTarget; | |
| } | |
| } | |
| // 2. Last open-panel dropzone (bottom-up order) | |
| for (let i = candidates.length - 1; i >= 0; i--) { | |
| const t = candidates[i]; | |
| const dz = document.querySelector(t.sel); | |
| if (dz && this._isDropzoneAccessible(dz)) return t; | |
| } | |
| // 3. Fallback | |
| return candidates[0]; | |
| }, | |
| /** | |
| * Check if a dropzone is accessible (visible, and its collapsible parent is open). | |
| */ | |
| _isDropzoneAccessible(dz) { | |
| const collapsible = dz.closest('.collapsible-content'); | |
| // Not inside a collapsible → always accessible | |
| if (!collapsible) return true; | |
| return collapsible.classList.contains('open'); | |
| }, | |
| init() { | |
| // Register generate page targets | |
| this.register('#extra-input-dropzone', 'generate', (file) => NAIExtraInput.handleUpload(file)); | |
| this.register('#reference-dropzone', 'generate', (file) => NAIReference.handleUpload(file)); | |
| // Reset last active target on page switch | |
| const origSwitch = NAIState._onPageSwitch; | |
| document.querySelectorAll('.nav-item').forEach(item => { | |
| item.addEventListener('click', () => { this._lastActiveTarget = null; }); | |
| }); | |
| // Global paste listener | |
| document.addEventListener('paste', (e) => { | |
| const active = document.activeElement; | |
| if (active) { | |
| const tag = active.tagName; | |
| if (tag === 'TEXTAREA') return; | |
| if (tag === 'INPUT' && ['text', 'number', 'search'].includes(active.type)) return; | |
| } | |
| const file = this._extractImageFile(e); | |
| if (!file) return; | |
| const target = this._findTarget(); | |
| if (target) { | |
| e.preventDefault(); | |
| target.handler(file); | |
| } | |
| }); | |
| }, | |
| }; |