๐Ÿ“„ File detail

hooks/useClipboardImageHint.ts

๐Ÿงฉ .ts๐Ÿ“ 78 lines๐Ÿ’พ 2,458 bytes๐Ÿ“ text
โ† Back to All Files

๐ŸŽฏ Use case

This file lives under โ€œhooks/โ€, which covers reusable UI or integration hooks. On the API surface it exposes useClipboardImageHint โ€” mainly functions, hooks, or classes. Dependencies touch React UI. It composes internal code from context, keybindings, and utils (relative imports).

Generated from folder role, exports, dependency roots, and inline comments โ€” not hand-reviewed for every path.

๐Ÿง  Inline summary

import { useEffect, useRef } from 'react' import { useNotifications } from '../context/notifications.js' import { getShortcutDisplay } from '../keybindings/shortcutFormat.js' import { hasImageInClipboard } from '../utils/imagePaste.js'

๐Ÿ“ค Exports (heuristic)

  • useClipboardImageHint

๐Ÿ“š External import roots

Package roots from from "โ€ฆ" (relative paths omitted).

  • react

๐Ÿ–ฅ๏ธ Source preview

import { useEffect, useRef } from 'react'
import { useNotifications } from '../context/notifications.js'
import { getShortcutDisplay } from '../keybindings/shortcutFormat.js'
import { hasImageInClipboard } from '../utils/imagePaste.js'

const NOTIFICATION_KEY = 'clipboard-image-hint'
// Small debounce to batch rapid focus changes
const FOCUS_CHECK_DEBOUNCE_MS = 1000
// Don't show the hint more than once per this interval
const HINT_COOLDOWN_MS = 30000

/**
 * Hook that shows a notification when the terminal regains focus
 * and the clipboard contains an image.
 *
 * @param isFocused - Whether the terminal is currently focused
 * @param enabled - Whether image paste is enabled (onImagePaste is defined)
 */
export function useClipboardImageHint(
  isFocused: boolean,
  enabled: boolean,
): void {
  const { addNotification } = useNotifications()
  const lastFocusedRef = useRef(isFocused)
  const lastHintTimeRef = useRef(0)
  const checkTimeoutRef = useRef<NodeJS.Timeout | null>(null)

  useEffect(() => {
    // Only trigger on focus regain (was unfocused, now focused)
    const wasFocused = lastFocusedRef.current
    lastFocusedRef.current = isFocused

    if (!enabled || !isFocused || wasFocused) {
      return
    }

    // Clear any pending check
    if (checkTimeoutRef.current) {
      clearTimeout(checkTimeoutRef.current)
    }

    // Small debounce to batch rapid focus changes
    checkTimeoutRef.current = setTimeout(
      async (checkTimeoutRef, lastHintTimeRef, addNotification) => {
        checkTimeoutRef.current = null

        // Check cooldown to avoid spamming the user
        const now = Date.now()
        if (now - lastHintTimeRef.current < HINT_COOLDOWN_MS) {
          return
        }

        // Check if clipboard has an image (async osascript call)
        if (await hasImageInClipboard()) {
          lastHintTimeRef.current = now
          addNotification({
            key: NOTIFICATION_KEY,
            text: `Image in clipboard ยท ${getShortcutDisplay('chat:imagePaste', 'Chat', 'ctrl+v')} to paste`,
            priority: 'immediate',
            timeoutMs: 8000,
          })
        }
      },
      FOCUS_CHECK_DEBOUNCE_MS,
      checkTimeoutRef,
      lastHintTimeRef,
      addNotification,
    )

    return () => {
      if (checkTimeoutRef.current) {
        clearTimeout(checkTimeoutRef.current)
        checkTimeoutRef.current = null
      }
    }
  }, [isFocused, enabled, addNotification])
}