develop #1
@@ -52,7 +52,7 @@ function CaptureForm() {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
text: `rec: ${title || url}\n\n${fullContent}`,
|
text: `web: ${title || url}\n\n${fullContent}`,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -44,13 +44,16 @@ export function BookmarkletInstructions() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
{isOpen && (
|
{!isOpen && (
|
||||||
<div onClick={() => setIsOpen(true)}>
|
<Button
|
||||||
<Button variant="outline" size="sm" className="gap-2">
|
variant="outline"
|
||||||
<Bookmark className="h-4 w-4" />
|
size="sm"
|
||||||
Capturar web
|
className="gap-2"
|
||||||
</Button>
|
onClick={() => setIsOpen(true)}
|
||||||
</div>
|
>
|
||||||
|
<Bookmark className="h-4 w-4" />
|
||||||
|
Capturar web
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
<DialogContent className="sm:max-w-md">
|
<DialogContent className="sm:max-w-md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
|
|||||||
@@ -1,16 +1,30 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { usePathname } from 'next/navigation'
|
import { usePathname } from 'next/navigation'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Plus, FileText, Settings, Menu, X } from 'lucide-react'
|
import { Plus, FileText, Settings, Menu, X } from 'lucide-react'
|
||||||
import { QuickAdd } from '@/components/quick-add'
|
import { QuickAdd } from '@/components/quick-add'
|
||||||
import { WorkModeToggle } from '@/components/work-mode-toggle'
|
import { WorkModeToggle } from '@/components/work-mode-toggle'
|
||||||
|
import { BookmarkletInstructions } from '@/components/bookmarklet-instructions'
|
||||||
|
import { isWorkModeEnabled } from '@/lib/preferences'
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||||
|
const [workModeToggleVisible, setWorkModeToggleVisible] = useState(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setWorkModeToggleVisible(isWorkModeEnabled())
|
||||||
|
|
||||||
|
const handlePreferencesChange = () => {
|
||||||
|
setWorkModeToggleVisible(isWorkModeEnabled())
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('preferences-updated', handlePreferencesChange)
|
||||||
|
return () => window.removeEventListener('preferences-updated', handlePreferencesChange)
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-40 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
<header className="sticky top-0 z-40 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||||
@@ -44,7 +58,8 @@ export function Header() {
|
|||||||
</nav>
|
</nav>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<QuickAdd />
|
<QuickAdd />
|
||||||
<WorkModeToggle />
|
<BookmarkletInstructions />
|
||||||
|
{workModeToggleVisible && <WorkModeToggle />}
|
||||||
<Link href="/new">
|
<Link href="/new">
|
||||||
<Button size="sm" className="gap-1.5">
|
<Button size="sm" className="gap-1.5">
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
@@ -59,9 +74,8 @@ export function Header() {
|
|||||||
<Link href="/" className="flex items-center gap-2">
|
<Link href="/" className="flex items-center gap-2">
|
||||||
<span className="text-lg font-bold">Recall</span>
|
<span className="text-lg font-bold">Recall</span>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-1.5">
|
||||||
<QuickAdd />
|
<QuickAdd />
|
||||||
<WorkModeToggle />
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -102,13 +116,20 @@ export function Header() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
<div className="border-t pt-2">
|
<div className="border-t pt-2 flex flex-col gap-1">
|
||||||
<Link href="/new" onClick={() => setMobileMenuOpen(false)}>
|
<Link href="/new" onClick={() => setMobileMenuOpen(false)}>
|
||||||
<Button size="sm" className="w-full justify-start gap-2">
|
<Button size="sm" className="w-full justify-start gap-2">
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
Nueva nota
|
Nueva nota
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
<BookmarkletInstructions />
|
||||||
|
{workModeToggleVisible && (
|
||||||
|
<div className="flex items-center justify-between px-2 py-1.5">
|
||||||
|
<span className="text-sm">Modo trabajo</span>
|
||||||
|
<WorkModeToggle />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { FeatureFlags, getFeatureFlags, setFeatureFlags } from '@/lib/preference
|
|||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import { BookmarkletInstructions } from '@/components/bookmarklet-instructions'
|
||||||
|
|
||||||
export function PreferencesPanel() {
|
export function PreferencesPanel() {
|
||||||
const [flags, setFlags] = useState<FeatureFlags>({
|
const [flags, setFlags] = useState<FeatureFlags>({
|
||||||
@@ -27,6 +28,8 @@ export function PreferencesPanel() {
|
|||||||
const handleWorkModeEnabled = (enabled: boolean) => {
|
const handleWorkModeEnabled = (enabled: boolean) => {
|
||||||
setFeatureFlags({ workModeEnabled: enabled })
|
setFeatureFlags({ workModeEnabled: enabled })
|
||||||
setFlags(getFeatureFlags())
|
setFlags(getFeatureFlags())
|
||||||
|
// Dispatch custom event to notify other components (like Header)
|
||||||
|
window.dispatchEvent(new CustomEvent('preferences-updated'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRetentionChange = (value: string) => {
|
const handleRetentionChange = (value: string) => {
|
||||||
@@ -97,10 +100,13 @@ export function PreferencesPanel() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pt-4 border-t">
|
<div className="pt-4 border-t">
|
||||||
<div className="flex flex-wrap gap-2">
|
<p className="text-sm font-medium mb-3">Integración externa</p>
|
||||||
|
<BookmarkletInstructions />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
<Badge variant="outline">Sprint MVP-5</Badge>
|
<Badge variant="outline">Sprint MVP-5</Badge>
|
||||||
<Badge variant="outline">v0.1.0</Badge>
|
<Badge variant="outline">v0.1.0</Badge>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import { useRouter } from 'next/navigation'
|
|||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { Textarea } from '@/components/ui/textarea'
|
import { Textarea } from '@/components/ui/textarea'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { Plus, Loader2, Text, Sparkles, X } from 'lucide-react'
|
import { Plus, Loader2, Sparkles, X, Text, ChevronDown } from 'lucide-react'
|
||||||
import { inferNoteType, formatContentForType } from '@/lib/type-inference'
|
import { inferNoteType, formatContentForType } from '@/lib/type-inference'
|
||||||
import { NoteType } from '@/types/note'
|
import { NoteType } from '@/types/note'
|
||||||
|
|
||||||
@@ -30,12 +31,13 @@ const TYPE_LABELS: Record<NoteType, string> = {
|
|||||||
export function QuickAdd() {
|
export function QuickAdd() {
|
||||||
const [value, setValue] = useState('')
|
const [value, setValue] = useState('')
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [isExpanded, setIsExpanded] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const [isMultiline, setIsMultiline] = useState(false)
|
const [isMultiline, setIsMultiline] = useState(false)
|
||||||
const [typeSuggestion, setTypeSuggestion] = useState<TypeSuggestion | null>(null)
|
const [typeSuggestion, setTypeSuggestion] = useState<TypeSuggestion | null>(null)
|
||||||
const [dismissedSuggestion, setDismissedSuggestion] = useState(false)
|
const [dismissedSuggestion, setDismissedSuggestion] = useState(false)
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||||
|
const popupRef = useRef<HTMLDivElement>(null)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const detectContentType = useCallback((text: string) => {
|
const detectContentType = useCallback((text: string) => {
|
||||||
@@ -58,7 +60,6 @@ export function QuickAdd() {
|
|||||||
}, [dismissedSuggestion])
|
}, [dismissedSuggestion])
|
||||||
|
|
||||||
const handlePaste = (e: React.ClipboardEvent) => {
|
const handlePaste = (e: React.ClipboardEvent) => {
|
||||||
// Let the paste happen first
|
|
||||||
setDismissedSuggestion(false)
|
setDismissedSuggestion(false)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
detectContentType(value)
|
detectContentType(value)
|
||||||
@@ -70,7 +71,7 @@ export function QuickAdd() {
|
|||||||
setValue(typeSuggestion.formattedContent)
|
setValue(typeSuggestion.formattedContent)
|
||||||
setTypeSuggestion(null)
|
setTypeSuggestion(null)
|
||||||
setIsMultiline(true)
|
setIsMultiline(true)
|
||||||
setIsExpanded(true)
|
setIsOpen(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +102,8 @@ export function QuickAdd() {
|
|||||||
description: note.title,
|
description: note.title,
|
||||||
})
|
})
|
||||||
setValue('')
|
setValue('')
|
||||||
setIsExpanded(false)
|
setIsOpen(false)
|
||||||
|
setIsMultiline(false)
|
||||||
router.refresh()
|
router.refresh()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Error', {
|
toast.error('Error', {
|
||||||
@@ -119,7 +121,7 @@ export function QuickAdd() {
|
|||||||
}
|
}
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
setValue('')
|
setValue('')
|
||||||
setIsExpanded(false)
|
setIsOpen(false)
|
||||||
setIsMultiline(false)
|
setIsMultiline(false)
|
||||||
inputRef.current?.blur()
|
inputRef.current?.blur()
|
||||||
textareaRef.current?.blur()
|
textareaRef.current?.blur()
|
||||||
@@ -129,25 +131,39 @@ export function QuickAdd() {
|
|||||||
const toggleMultiline = () => {
|
const toggleMultiline = () => {
|
||||||
setIsMultiline(!isMultiline)
|
setIsMultiline(!isMultiline)
|
||||||
if (!isMultiline) {
|
if (!isMultiline) {
|
||||||
setIsExpanded(true)
|
|
||||||
setTimeout(() => textareaRef.current?.focus(), 0)
|
setTimeout(() => textareaRef.current?.focus(), 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleInputFocus = () => {
|
||||||
|
setIsOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close popup when clicking outside
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (e: MouseEvent) => {
|
||||||
|
if (popupRef.current && !popupRef.current.contains(e.target as Node)) {
|
||||||
|
setIsOpen(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isOpen) {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside)
|
||||||
|
}
|
||||||
|
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||||
|
}, [isOpen])
|
||||||
|
|
||||||
// Focus on keyboard shortcut
|
// Focus on keyboard shortcut
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleGlobalKeyDown = (e: KeyboardEvent) => {
|
const handleGlobalKeyDown = (e: KeyboardEvent) => {
|
||||||
// Ctrl+N or Cmd+N to focus quick add
|
|
||||||
if ((e.key === 'n' && (e.metaKey || e.ctrlKey)) || (e.key === 'n' && e.altKey)) {
|
if ((e.key === 'n' && (e.metaKey || e.ctrlKey)) || (e.key === 'n' && e.altKey)) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
inputRef.current?.focus()
|
inputRef.current?.focus()
|
||||||
inputRef.current?.select()
|
inputRef.current?.select()
|
||||||
setIsExpanded(true)
|
setIsOpen(true)
|
||||||
}
|
}
|
||||||
// Escape to blur
|
|
||||||
if (e.key === 'Escape' && document.activeElement === inputRef.current) {
|
if (e.key === 'Escape' && document.activeElement === inputRef.current) {
|
||||||
inputRef.current?.blur()
|
inputRef.current?.blur()
|
||||||
setIsExpanded(false)
|
setIsOpen(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window.addEventListener('keydown', handleGlobalKeyDown)
|
window.addEventListener('keydown', handleGlobalKeyDown)
|
||||||
@@ -155,115 +171,128 @@ export function QuickAdd() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative" ref={popupRef}>
|
||||||
<form onSubmit={handleSubmit} className="flex items-end gap-2">
|
{/* Compact input row */}
|
||||||
<div className="relative flex-1">
|
<form onSubmit={handleSubmit} className="flex items-center gap-1.5">
|
||||||
{isMultiline ? (
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
placeholder="cmd: título..."
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => {
|
||||||
|
setValue(e.target.value)
|
||||||
|
detectContentType(e.target.value)
|
||||||
|
if (e.target.value) setIsOpen(true)
|
||||||
|
}}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onFocus={handleInputFocus}
|
||||||
|
onPaste={handlePaste}
|
||||||
|
className="w-full sm:w-80 h-9 pr-16"
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
{isLoading && (
|
||||||
|
<Loader2 className="absolute right-2 top-1/2 -translate-y-1/2 h-4 w-4 animate-spin text-muted-foreground" />
|
||||||
|
)}
|
||||||
|
{/* Action buttons inside input */}
|
||||||
|
<div className="absolute right-1 top-1/2 -translate-y-1/2 flex items-center gap-0.5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={toggleMultiline}
|
||||||
|
className={cn(
|
||||||
|
'p-1 rounded hover:bg-accent transition-colors',
|
||||||
|
isMultiline && 'bg-accent text-accent-foreground'
|
||||||
|
)}
|
||||||
|
title={isMultiline ? 'Modo línea' : 'Modo multilínea'}
|
||||||
|
>
|
||||||
|
{isMultiline ? (
|
||||||
|
<ChevronDown className="h-3.5 w-3.5" />
|
||||||
|
) : (
|
||||||
|
<Text className="h-3.5 w-3.5" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={!value.trim() || isLoading}
|
||||||
|
className={cn(
|
||||||
|
'p-1 rounded hover:bg-accent transition-colors',
|
||||||
|
'disabled:pointer-events-none disabled:opacity-30'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Plus className="h-3.5 w-3.5" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Expanded popup */}
|
||||||
|
{isOpen && (
|
||||||
|
<div className="absolute top-full left-0 right-0 mt-2 p-3 bg-popover border rounded-lg shadow-lg z-50">
|
||||||
|
{/* Multiline textarea (shown when multiline mode) */}
|
||||||
|
{isMultiline && (
|
||||||
<Textarea
|
<Textarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
placeholder="cmd: título #tag Contenido multilínea..."
|
placeholder="Contenido multilínea..."
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setValue(e.target.value)
|
setValue(e.target.value)
|
||||||
detectContentType(e.target.value)
|
detectContentType(e.target.value)
|
||||||
}}
|
}}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onFocus={() => setIsExpanded(true)}
|
|
||||||
onPaste={handlePaste}
|
onPaste={handlePaste}
|
||||||
className={cn(
|
className="min-h-[100px] max-h-[200px] resize-none w-full"
|
||||||
'min-h-[80px] max-h-[200px] transition-all duration-200 resize-none',
|
|
||||||
isExpanded && 'w-full'
|
|
||||||
)}
|
|
||||||
disabled={isLoading}
|
|
||||||
rows={isExpanded ? 4 : 2}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Input
|
|
||||||
ref={inputRef}
|
|
||||||
type="text"
|
|
||||||
placeholder="cmd: título #tag..."
|
|
||||||
value={value}
|
|
||||||
onChange={(e) => setValue(e.target.value)}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
onFocus={() => setIsExpanded(true)}
|
|
||||||
onPaste={handlePaste}
|
|
||||||
className={cn(
|
|
||||||
'w-24 xs:w-32 sm:w-48 transition-all duration-200',
|
|
||||||
isExpanded && 'w-40 xs:w-48 sm:w-72'
|
|
||||||
)}
|
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isLoading && (
|
|
||||||
<Loader2 className="absolute right-2 top-1/2 -translate-y-1/2 h-4 w-4 animate-spin text-muted-foreground" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={toggleMultiline}
|
|
||||||
className={cn(
|
|
||||||
'inline-flex items-center justify-center rounded-lg border bg-background p-1.5 sm:p-2',
|
|
||||||
'hover:bg-accent hover:text-accent-foreground',
|
|
||||||
'transition-colors',
|
|
||||||
isMultiline && 'bg-accent text-accent-foreground'
|
|
||||||
)}
|
|
||||||
title={isMultiline ? 'Modo línea' : 'Modo multilínea'}
|
|
||||||
>
|
|
||||||
<Text className="h-3 w-3 sm:h-4 sm:w-4" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={!value.trim() || isLoading}
|
|
||||||
className={cn(
|
|
||||||
'inline-flex items-center justify-center rounded-lg border bg-background p-1.5 sm:p-2',
|
|
||||||
'hover:bg-accent hover:text-accent-foreground',
|
|
||||||
'disabled:pointer-events-none disabled:opacity-50',
|
|
||||||
'transition-colors'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{isLoading ? (
|
|
||||||
<Loader2 className="h-3 w-3 sm:h-4 sm:w-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<Plus className="h-3 w-3 sm:h-4 sm:w-4" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{/* Smart paste suggestion */}
|
{/* Smart paste suggestion */}
|
||||||
{typeSuggestion && (
|
{typeSuggestion && (
|
||||||
<div className="absolute top-full left-0 right-0 mt-2 p-3 bg-popover border rounded-lg shadow-md z-50">
|
<div className="mt-2 p-2 bg-muted/50 rounded-lg border">
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<Sparkles className="h-4 w-4 text-primary mt-0.5 flex-shrink-0" />
|
<Sparkles className="h-4 w-4 text-primary mt-0.5 flex-shrink-0" />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm font-medium">
|
<p className="text-sm font-medium">
|
||||||
Detectado: <span className="text-primary">{TYPE_LABELS[typeSuggestion.type]}</span>
|
Detectado: <span className="text-primary">{TYPE_LABELS[typeSuggestion.type]}</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground mt-0.5">{typeSuggestion.reason}</p>
|
<p className="text-xs text-muted-foreground mt-0.5">{typeSuggestion.reason}</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={dismissSuggestion}
|
||||||
|
className="p-1 hover:bg-accent rounded"
|
||||||
|
>
|
||||||
|
<X className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 flex gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={acceptSuggestion}
|
||||||
|
className="text-xs px-2 py-1 bg-primary text-primary-foreground rounded hover:bg-primary/90"
|
||||||
|
>
|
||||||
|
Usar plantilla
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={dismissSuggestion}
|
||||||
|
className="text-xs px-2 py-1 text-muted-foreground hover:bg-accent rounded"
|
||||||
|
>
|
||||||
|
Descartar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
)}
|
||||||
type="button"
|
|
||||||
onClick={dismissSuggestion}
|
{/* Help text */}
|
||||||
className="p-1 hover:bg-accent rounded"
|
{!value && !typeSuggestion && (
|
||||||
>
|
<div className="text-xs text-muted-foreground">
|
||||||
<X className="h-3 w-3" />
|
Usa prefijos como <span className="font-mono bg-muted px-1 rounded">cmd:</span>, <span className="font-mono bg-muted px-1 rounded">snip:</span> para тип notes
|
||||||
</button>
|
</div>
|
||||||
</div>
|
)}
|
||||||
<div className="mt-2 flex gap-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={acceptSuggestion}
|
|
||||||
className="text-xs px-2 py-1 bg-primary text-primary-foreground rounded hover:bg-primary/90"
|
|
||||||
>
|
|
||||||
Usar plantilla
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={dismissSuggestion}
|
|
||||||
className="text-xs px-2 py-1 text-muted-foreground hover:bg-accent rounded"
|
|
||||||
>
|
|
||||||
Descartar
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -51,12 +51,16 @@ export function encodeCapturePayload(payload: CapturePayload): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function generateBookmarklet(): string {
|
export function generateBookmarklet(): string {
|
||||||
|
// Get the current origin (where the app is running)
|
||||||
|
const origin = typeof window !== 'undefined' ? window.location.origin : ''
|
||||||
|
|
||||||
const code = `
|
const code = `
|
||||||
var title = document.title;
|
var title = document.title;
|
||||||
var url = location.href;
|
var url = location.href;
|
||||||
var selection = window.getSelection().toString();
|
var selection = window.getSelection().toString();
|
||||||
var params = new URLSearchParams({title, url, selection});
|
var params = new URLSearchParams({title, url, selection});
|
||||||
window.open('/capture?' + params.toString(), '_blank');
|
var base = ${JSON.stringify(origin)};
|
||||||
|
window.open(base + '/capture?' + params.toString(), '_blank');
|
||||||
`.replace(/\s+/g, ' ').trim()
|
`.replace(/\s+/g, ' ').trim()
|
||||||
return `javascript:${code}`
|
return `javascript:${code}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const TYPE_PREFIXES: Record<string, NoteType> = {
|
|||||||
'rec:': 'recipe',
|
'rec:': 'recipe',
|
||||||
'proc:': 'procedure',
|
'proc:': 'procedure',
|
||||||
'inv:': 'inventory',
|
'inv:': 'inventory',
|
||||||
|
'web:': 'note',
|
||||||
}
|
}
|
||||||
|
|
||||||
const TAG_REGEX = /#([a-z0-9]+)/g
|
const TAG_REGEX = /#([a-z0-9]+)/g
|
||||||
|
|||||||
Reference in New Issue
Block a user