feat: MVP-5 Sprint 4 - External Capture via Bookmarklet
- Add bookmarklet for capturing web content from any page - Add capture confirmation page with edit before save - Add secure /api/capture endpoint with rate limiting - Add bookmarklet instructions component with drag-and-drop
This commit is contained in:
132
src/components/bookmarklet-instructions.tsx
Normal file
132
src/components/bookmarklet-instructions.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import { generateBookmarklet } from '@/lib/external-capture'
|
||||
import { Bookmark, Copy, Check, Info } from 'lucide-react'
|
||||
|
||||
export function BookmarkletInstructions() {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [copied, setCopied] = useState(false)
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
|
||||
const bookmarkletCode = generateBookmarklet()
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(bookmarkletCode)
|
||||
setCopied(true)
|
||||
toast.success('Código copiado al portapapeles')
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
} catch {
|
||||
toast.error('Error al copiar el código')
|
||||
}
|
||||
}
|
||||
|
||||
const handleDragStart = (e: React.DragEvent) => {
|
||||
e.dataTransfer.setData('text/plain', bookmarkletCode)
|
||||
e.dataTransfer.effectAllowed = 'copy'
|
||||
setIsDragging(true)
|
||||
}
|
||||
|
||||
const handleDragEnd = () => {
|
||||
setIsDragging(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
{isOpen && (
|
||||
<div onClick={() => setIsOpen(true)}>
|
||||
<Button variant="outline" size="sm" className="gap-2">
|
||||
<Bookmark className="h-4 w-4" />
|
||||
Capturar web
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Bookmark className="h-5 w-5" />
|
||||
Capturar desde web
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Guarda contenido de cualquier página web directamente en tus notas.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="bg-muted/50 rounded-lg p-4">
|
||||
<p className="text-sm font-medium mb-2">Instrucciones:</p>
|
||||
<ol className="text-sm text-muted-foreground space-y-2 list-decimal list-inside">
|
||||
<li>Arrastra el botón de abajo a tu barra de marcadores</li>
|
||||
<li>Cuando quieras capturar algo, haz clic en el marcador</li>
|
||||
<li>Se abrirá una página para confirmar y guardar</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-sm font-medium">Botón del marcador:</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center p-4 bg-muted/30 rounded-lg border-2 border-dashed border-muted">
|
||||
<button
|
||||
draggable
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onClick={(e) => e.preventDefault()}
|
||||
className={`px-4 py-2 bg-primary text-primary-foreground rounded-lg text-sm font-medium cursor-grab active:cursor-grabbing transition-all ${
|
||||
isDragging ? 'opacity-50 scale-95' : ''
|
||||
}`}
|
||||
title="Arrastra esto a tu barra de marcadores"
|
||||
>
|
||||
Capturar a Recall
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-muted-foreground text-center">
|
||||
No puedes arrastrar? Usa el botón copiar y crea un marcador manualmente.
|
||||
</p>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1 gap-2"
|
||||
onClick={handleCopy}
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<Check className="h-4 w-4" />
|
||||
Copiado
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="h-4 w-4" />
|
||||
Copiar código
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="bg-muted/30 rounded-lg p-3">
|
||||
<div className="flex items-start gap-2">
|
||||
<Info className="h-4 w-4 mt-0.5 text-muted-foreground flex-shrink-0" />
|
||||
<p className="text-xs text-muted-foreground">
|
||||
El marcador capturará el título de la página, la URL y cualquier texto que hayas seleccionado antes de hacer clic.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user