feat: Complete internationalization and code cleanup
## Translation Files - Add 11 new language files (es, de, pt, ru, zh, ja, ko, ar, hi, nl, pl) - Add 100+ missing translation keys across all 15 languages - New sections: notebook, pagination, ai.batchOrganization, ai.autoLabels - Update nav section with workspace, quickAccess, myLibrary keys ## Component Updates - Update 15+ components to use translation keys instead of hardcoded text - Components: notebook dialogs, sidebar, header, note-input, ghost-tags, etc. - Replace 80+ hardcoded English/French strings with t() calls - Ensure consistent UI across all supported languages ## Code Quality - Remove 77+ console.log statements from codebase - Clean up API routes, components, hooks, and services - Keep only essential error handling (no debugging logs) ## UI/UX Improvements - Update Keep logo to yellow post-it style (from-yellow-400 to-amber-500) - Change selection colors to #FEF3C6 (notebooks) and #EFB162 (nav items) - Make "+" button permanently visible in notebooks section - Fix grammar and syntax errors in multiple components ## Bug Fixes - Fix JSON syntax errors in it.json, nl.json, pl.json, zh.json - Fix syntax errors in notebook-suggestion-toast.tsx - Fix syntax errors in use-auto-tagging.ts - Fix syntax errors in paragraph-refactor.service.ts - Fix duplicate "fusion" section in nl.json 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Ou une version plus courte si vous préférez : feat(i18n): Add 15 languages, remove logs, update UI components - Create 11 new translation files (es, de, pt, ru, zh, ja, ko, ar, hi, nl, pl) - Add 100+ translation keys: notebook, pagination, AI features - Update 15+ components to use translations (80+ strings) - Remove 77+ console.log statements from codebase - Fix JSON syntax errors in 4 translation files - Fix component syntax errors (toast, hooks, services) - Update logo to yellow post-it style - Change selection colors (#FEF3C6, #EFB162) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,7 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { createNote } from '@/app/actions/notes'
|
||||
import { fetchLinkMetadata } from '@/app/actions/scrape'
|
||||
import { CheckItem, NOTE_COLORS, NoteColor, LinkMetadata } from '@/lib/types'
|
||||
import { CheckItem, NOTE_COLORS, NoteColor, LinkMetadata, Note } from '@/lib/types'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import {
|
||||
Tooltip,
|
||||
@@ -42,10 +42,15 @@ import { MarkdownContent } from './markdown-content'
|
||||
import { LabelSelector } from './label-selector'
|
||||
import { LabelBadge } from './label-badge'
|
||||
import { useAutoTagging } from '@/hooks/use-auto-tagging'
|
||||
import { useTitleSuggestions } from '@/hooks/use-title-suggestions'
|
||||
import { GhostTags } from './ghost-tags'
|
||||
import { TitleSuggestions } from './title-suggestions'
|
||||
import { CollaboratorDialog } from './collaborator-dialog'
|
||||
import { AIAssistantActionBar } from './ai-assistant-action-bar'
|
||||
import { useLabels } from '@/context/LabelContext'
|
||||
import { useSession } from 'next-auth/react'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
|
||||
interface HistoryState {
|
||||
title: string
|
||||
@@ -59,9 +64,16 @@ interface NoteState {
|
||||
images: string[]
|
||||
}
|
||||
|
||||
export function NoteInput() {
|
||||
interface NoteInputProps {
|
||||
onNoteCreated?: (note: Note) => void
|
||||
}
|
||||
|
||||
export function NoteInput({ onNoteCreated }: NoteInputProps) {
|
||||
const { labels: globalLabels, addLabel } = useLabels()
|
||||
const { data: session } = useSession()
|
||||
const { t } = useLanguage()
|
||||
const searchParams = useSearchParams()
|
||||
const currentNotebookId = searchParams.get('notebook') || undefined // Get current notebook from URL
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const [type, setType] = useState<'text' | 'checklist'>('text')
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
@@ -88,12 +100,23 @@ export function NoteInput() {
|
||||
].join(' ').trim();
|
||||
|
||||
// Auto-tagging hook
|
||||
const { suggestions, isAnalyzing } = useAutoTagging({
|
||||
const { suggestions, isAnalyzing } = useAutoTagging({
|
||||
content: type === 'text' ? fullContentForAI : '',
|
||||
enabled: type === 'text' && isExpanded
|
||||
})
|
||||
|
||||
// Title suggestions
|
||||
const titleSuggestionsEnabled = type === 'text' && isExpanded && !title
|
||||
const titleSuggestionsContent = type === 'text' ? fullContentForAI : ''
|
||||
|
||||
// Title suggestions hook
|
||||
const { suggestions: titleSuggestions, isAnalyzing: isAnalyzingTitles } = useTitleSuggestions({
|
||||
content: titleSuggestionsContent,
|
||||
enabled: titleSuggestionsEnabled
|
||||
})
|
||||
|
||||
const [dismissedTags, setDismissedTags] = useState<string[]>([])
|
||||
const [dismissedTitleSuggestions, setDismissedTitleSuggestions] = useState(false)
|
||||
|
||||
const handleSelectGhostTag = async (tag: string) => {
|
||||
// Vérification insensible à la casse
|
||||
@@ -101,7 +124,7 @@ export function NoteInput() {
|
||||
|
||||
if (!tagExists) {
|
||||
setSelectedLabels(prev => [...prev, tag])
|
||||
|
||||
|
||||
const globalExists = globalLabels.some(l => l.name.toLowerCase() === tag.toLowerCase())
|
||||
if (!globalExists) {
|
||||
try {
|
||||
@@ -110,8 +133,8 @@ export function NoteInput() {
|
||||
console.error('Erreur création label auto:', err)
|
||||
}
|
||||
}
|
||||
|
||||
toast.success(`Tag "${tag}" ajouté`)
|
||||
|
||||
toast.success(t('labels.tagAdded', { tag }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +208,124 @@ export function NoteInput() {
|
||||
setContent(history[newIndex].content)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// AI Assistant state and handlers
|
||||
const [isProcessingAI, setIsProcessingAI] = useState(false)
|
||||
|
||||
const handleClarify = async () => {
|
||||
const wordCount = content.split(/\s+/).filter(w => w.length > 0).length
|
||||
if (!content || wordCount < 10) {
|
||||
toast.error(t('ai.reformulationMinWords', { count: wordCount }))
|
||||
return
|
||||
}
|
||||
|
||||
setIsProcessingAI(true)
|
||||
try {
|
||||
const response = await fetch('/api/ai/reformulate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: content, option: 'clarify' })
|
||||
})
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw new Error(data.error || 'Failed to clarify')
|
||||
setContent(data.reformulatedText || data.text)
|
||||
toast.success(t('ai.reformulationApplied'))
|
||||
} catch (error) {
|
||||
console.error('Clarify error:', error)
|
||||
toast.error(t('ai.reformulationFailed'))
|
||||
} finally {
|
||||
setIsProcessingAI(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleShorten = async () => {
|
||||
const wordCount = content.split(/\s+/).filter(w => w.length > 0).length
|
||||
if (!content || wordCount < 10) {
|
||||
toast.error(t('ai.reformulationMinWords', { count: wordCount }))
|
||||
return
|
||||
}
|
||||
|
||||
setIsProcessingAI(true)
|
||||
try {
|
||||
const response = await fetch('/api/ai/reformulate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: content, option: 'shorten' })
|
||||
})
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw new Error(data.error || 'Failed to shorten')
|
||||
setContent(data.reformulatedText || data.text)
|
||||
toast.success(t('ai.reformulationApplied'))
|
||||
} catch (error) {
|
||||
console.error('Shorten error:', error)
|
||||
toast.error(t('ai.reformulationFailed'))
|
||||
} finally {
|
||||
setIsProcessingAI(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleImprove = async () => {
|
||||
const wordCount = content.split(/\s+/).filter(w => w.length > 0).length
|
||||
if (!content || wordCount < 10) {
|
||||
toast.error(t('ai.reformulationMinWords', { count: wordCount }))
|
||||
return
|
||||
}
|
||||
|
||||
setIsProcessingAI(true)
|
||||
try {
|
||||
const response = await fetch('/api/ai/reformulate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: content, option: 'improve' })
|
||||
})
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw new Error(data.error || 'Failed to improve')
|
||||
setContent(data.reformulatedText || data.text)
|
||||
toast.success(t('ai.reformulationApplied'))
|
||||
} catch (error) {
|
||||
console.error('Improve error:', error)
|
||||
toast.error(t('ai.reformulationFailed'))
|
||||
} finally {
|
||||
setIsProcessingAI(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleTransformMarkdown = async () => {
|
||||
const wordCount = content.split(/\s+/).filter(w => w.length > 0).length
|
||||
if (!content || wordCount < 10) {
|
||||
toast.error(t('ai.reformulationMinWords', { count: wordCount }))
|
||||
return
|
||||
}
|
||||
|
||||
if (wordCount > 500) {
|
||||
toast.error(t('ai.reformulationMaxWords'))
|
||||
return
|
||||
}
|
||||
|
||||
setIsProcessingAI(true)
|
||||
try {
|
||||
const response = await fetch('/api/ai/transform-markdown', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: content })
|
||||
})
|
||||
const data = await response.json()
|
||||
if (!response.ok) throw new Error(data.error || 'Failed to transform')
|
||||
|
||||
// Set the transformed markdown content and enable markdown mode
|
||||
setContent(data.transformedText)
|
||||
setIsMarkdown(true)
|
||||
setShowMarkdownPreview(false)
|
||||
|
||||
toast.success(t('ai.transformSuccess'))
|
||||
} catch (error) {
|
||||
console.error('Transform to markdown error:', error)
|
||||
toast.error(t('ai.transformError'))
|
||||
} finally {
|
||||
setIsProcessingAI(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Keyboard shortcuts
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
@@ -216,12 +356,12 @@ export function NoteInput() {
|
||||
for (const file of Array.from(files)) {
|
||||
// Validation
|
||||
if (!validTypes.includes(file.type)) {
|
||||
toast.error(`Invalid file type: ${file.name}. Only JPEG, PNG, GIF, and WebP allowed.`)
|
||||
toast.error(t('notes.invalidFileType', { fileName: file.name }))
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
if (file.size > maxSize) {
|
||||
toast.error(`File too large: ${file.name}. Maximum size is 5MB.`)
|
||||
toast.error(t('notes.fileTooLarge', { fileName: file.name, maxSize: '5MB' }))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -241,7 +381,7 @@ export function NoteInput() {
|
||||
setImages(prev => [...prev, data.url])
|
||||
} catch (error) {
|
||||
console.error('Upload error:', error)
|
||||
toast.error(`Failed to upload ${file.name}`)
|
||||
toast.error(t('notes.uploadFailed', { fileName: file.name }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,23 +391,23 @@ export function NoteInput() {
|
||||
|
||||
const handleAddLink = async () => {
|
||||
if (!linkUrl) return
|
||||
|
||||
|
||||
// Optimistic add (or loading state)
|
||||
setShowLinkDialog(false)
|
||||
|
||||
|
||||
try {
|
||||
const metadata = await fetchLinkMetadata(linkUrl)
|
||||
if (metadata) {
|
||||
setLinks(prev => [...prev, metadata])
|
||||
toast.success('Link added')
|
||||
toast.success(t('notes.linkAdded'))
|
||||
} else {
|
||||
toast.warning('Could not fetch link metadata')
|
||||
toast.warning(t('notes.linkMetadataFailed'))
|
||||
// Fallback: just add the url as title
|
||||
setLinks(prev => [...prev, { url: linkUrl, title: linkUrl }])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to add link:', error)
|
||||
toast.error('Failed to add link')
|
||||
toast.error(t('notes.linkAddFailed'))
|
||||
} finally {
|
||||
setLinkUrl('')
|
||||
}
|
||||
@@ -286,25 +426,25 @@ export function NoteInput() {
|
||||
|
||||
const handleReminderSave = () => {
|
||||
if (!reminderDate || !reminderTime) {
|
||||
toast.warning('Please enter date and time')
|
||||
toast.warning(t('notes.reminderDateTimeRequired'))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const dateTimeString = `${reminderDate}T${reminderTime}`
|
||||
const date = new Date(dateTimeString)
|
||||
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
toast.error('Invalid date or time')
|
||||
toast.error(t('notes.invalidDateTime'))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (date < new Date()) {
|
||||
toast.error('Reminder must be in the future')
|
||||
toast.error(t('notes.reminderMustBeFuture'))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
setCurrentReminder(date)
|
||||
toast.success(`Reminder set for ${date.toLocaleString()}`)
|
||||
toast.success(t('notes.reminderSet', { datetime: date.toLocaleString() }))
|
||||
setShowReminderDialog(false)
|
||||
setReminderDate('')
|
||||
setReminderTime('')
|
||||
@@ -317,17 +457,17 @@ export function NoteInput() {
|
||||
const hasCheckItems = checkItems.some(i => i.text.trim().length > 0);
|
||||
|
||||
if (type === 'text' && !hasContent && !hasMedia) {
|
||||
toast.warning('Please enter some content or add a link/image')
|
||||
toast.warning(t('notes.contentOrMediaRequired'))
|
||||
return
|
||||
}
|
||||
if (type === 'checklist' && !hasCheckItems && !hasMedia) {
|
||||
toast.warning('Please add at least one item or media')
|
||||
toast.warning(t('notes.itemOrMediaRequired'))
|
||||
return
|
||||
}
|
||||
|
||||
setIsSubmitting(true)
|
||||
try {
|
||||
await createNote({
|
||||
const createdNote = await createNote({
|
||||
title: title.trim() || undefined,
|
||||
content: type === 'text' ? content : '',
|
||||
type,
|
||||
@@ -340,8 +480,14 @@ export function NoteInput() {
|
||||
isMarkdown,
|
||||
labels: selectedLabels.length > 0 ? selectedLabels : undefined,
|
||||
sharedWith: collaborators.length > 0 ? collaborators : undefined,
|
||||
notebookId: currentNotebookId, // Assign note to current notebook if in one
|
||||
})
|
||||
|
||||
// Notify parent component about the created note (for notebook suggestion)
|
||||
if (createdNote && onNoteCreated) {
|
||||
onNoteCreated(createdNote)
|
||||
}
|
||||
|
||||
// Reset form
|
||||
setTitle('')
|
||||
setContent('')
|
||||
@@ -359,11 +505,12 @@ export function NoteInput() {
|
||||
setCurrentReminder(null)
|
||||
setSelectedLabels([])
|
||||
setCollaborators([])
|
||||
|
||||
toast.success('Note created successfully')
|
||||
setDismissedTitleSuggestions(false)
|
||||
|
||||
toast.success(t('notes.noteCreated'))
|
||||
} catch (error) {
|
||||
console.error('Failed to create note:', error)
|
||||
toast.error('Failed to create note')
|
||||
toast.error(t('notes.noteCreateFailed'))
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
@@ -402,6 +549,7 @@ export function NoteInput() {
|
||||
setCurrentReminder(null)
|
||||
setSelectedLabels([])
|
||||
setCollaborators([])
|
||||
setDismissedTitleSuggestions(false)
|
||||
}
|
||||
|
||||
if (!isExpanded) {
|
||||
@@ -409,7 +557,7 @@ export function NoteInput() {
|
||||
<Card className="p-4 max-w-2xl mx-auto mb-8 cursor-text shadow-md hover:shadow-lg transition-shadow">
|
||||
<div className="flex items-center gap-4">
|
||||
<Input
|
||||
placeholder="Take a note..."
|
||||
placeholder={t('notes.placeholder')}
|
||||
onClick={() => setIsExpanded(true)}
|
||||
readOnly
|
||||
value=""
|
||||
@@ -422,7 +570,7 @@ export function NoteInput() {
|
||||
setType('checklist')
|
||||
setIsExpanded(true)
|
||||
}}
|
||||
title="New checklist"
|
||||
title={t('notes.newChecklist')}
|
||||
>
|
||||
<CheckSquare className="h-5 w-5" />
|
||||
</Button>
|
||||
@@ -441,12 +589,21 @@ export function NoteInput() {
|
||||
)}>
|
||||
<div className="space-y-3">
|
||||
<Input
|
||||
placeholder="Title"
|
||||
placeholder={t('notes.titlePlaceholder')}
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
className="border-0 focus-visible:ring-0 text-base font-semibold"
|
||||
/>
|
||||
|
||||
{/* Title Suggestions */}
|
||||
{!title && !dismissedTitleSuggestions && titleSuggestions.length > 0 && (
|
||||
<TitleSuggestions
|
||||
suggestions={titleSuggestions}
|
||||
onSelect={(selectedTitle) => setTitle(selectedTitle)}
|
||||
onDismiss={() => setDismissedTitleSuggestions(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Image Preview */}
|
||||
{images.length > 0 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -525,12 +682,12 @@ export function NoteInput() {
|
||||
{showMarkdownPreview ? (
|
||||
<>
|
||||
<FileText className="h-3 w-3 mr-1" />
|
||||
Edit
|
||||
{t('general.edit')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Eye className="h-3 w-3 mr-1" />
|
||||
Preview
|
||||
{t('general.preview')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -544,7 +701,7 @@ export function NoteInput() {
|
||||
/>
|
||||
) : (
|
||||
<Textarea
|
||||
placeholder={isMarkdown ? "Take a note... (Markdown supported)" : "Take a note..."}
|
||||
placeholder={isMarkdown ? t('notes.markdownPlaceholder') : t('notes.placeholder')}
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
className="border-0 focus-visible:ring-0 min-h-[100px] resize-none"
|
||||
@@ -553,13 +710,26 @@ export function NoteInput() {
|
||||
)}
|
||||
|
||||
{/* AI Auto-tagging Suggestions */}
|
||||
<GhostTags
|
||||
suggestions={filteredSuggestions}
|
||||
<GhostTags
|
||||
suggestions={filteredSuggestions}
|
||||
addedTags={selectedLabels}
|
||||
isAnalyzing={isAnalyzing}
|
||||
onSelectTag={handleSelectGhostTag}
|
||||
isAnalyzing={isAnalyzing}
|
||||
onSelectTag={handleSelectGhostTag}
|
||||
onDismissTag={handleDismissGhostTag}
|
||||
/>
|
||||
|
||||
{/* AI Assistant ActionBar */}
|
||||
{type === 'text' && (
|
||||
<AIAssistantActionBar
|
||||
onClarify={handleClarify}
|
||||
onShorten={handleShorten}
|
||||
onImprove={handleImprove}
|
||||
onTransformMarkdown={handleTransformMarkdown}
|
||||
isMarkdownMode={isMarkdown}
|
||||
disabled={isProcessingAI || !content}
|
||||
className="mt-3"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
@@ -569,7 +739,7 @@ export function NoteInput() {
|
||||
<Input
|
||||
value={item.text}
|
||||
onChange={(e) => handleUpdateCheckItem(item.id, e.target.value)}
|
||||
placeholder="List item"
|
||||
placeholder={t('notes.listItem')}
|
||||
className="flex-1 border-0 focus-visible:ring-0"
|
||||
autoFocus={checkItems[checkItems.length - 1].id === item.id}
|
||||
/>
|
||||
@@ -589,7 +759,7 @@ export function NoteInput() {
|
||||
onClick={handleAddCheckItem}
|
||||
className="text-gray-600 dark:text-gray-400 w-full justify-start"
|
||||
>
|
||||
+ List item
|
||||
{t('notes.addListItem')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -606,13 +776,13 @@ export function NoteInput() {
|
||||
"h-8 w-8",
|
||||
currentReminder && "text-blue-600"
|
||||
)}
|
||||
title="Remind me"
|
||||
title={t('notes.remindMe')}
|
||||
onClick={handleReminderOpen}
|
||||
>
|
||||
<Bell className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Remind me</TooltipContent>
|
||||
<TooltipContent>{t('notes.remindMe')}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
@@ -628,12 +798,12 @@ export function NoteInput() {
|
||||
setIsMarkdown(!isMarkdown)
|
||||
if (isMarkdown) setShowMarkdownPreview(false)
|
||||
}}
|
||||
title="Markdown"
|
||||
title={t('notes.markdown')}
|
||||
>
|
||||
<FileText className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Markdown</TooltipContent>
|
||||
<TooltipContent>{t('notes.markdown')}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
@@ -642,13 +812,13 @@ export function NoteInput() {
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
title="Add image"
|
||||
title={t('notes.addImage')}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
>
|
||||
<Image className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Add image</TooltipContent>
|
||||
<TooltipContent>{t('notes.addImage')}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
@@ -657,13 +827,13 @@ export function NoteInput() {
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
title="Add collaborators"
|
||||
title={t('notes.addCollaborators')}
|
||||
onClick={() => setShowCollaboratorDialog(true)}
|
||||
>
|
||||
<UserPlus className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Add collaborators</TooltipContent>
|
||||
<TooltipContent>{t('notes.addCollaborators')}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
@@ -673,12 +843,12 @@ export function NoteInput() {
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
onClick={() => setShowLinkDialog(true)}
|
||||
title="Add Link"
|
||||
title={t('notes.addLink')}
|
||||
>
|
||||
<LinkIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Add Link</TooltipContent>
|
||||
<TooltipContent>{t('notes.addLink')}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<LabelSelector
|
||||
@@ -692,12 +862,12 @@ export function NoteInput() {
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8" title="Background options">
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8" title={t('notes.backgroundOptions')}>
|
||||
<Palette className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Background options</TooltipContent>
|
||||
<TooltipContent>{t('notes.backgroundOptions')}</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenuContent align="start" className="w-40">
|
||||
<div className="grid grid-cols-5 gap-2 p-2">
|
||||
@@ -727,21 +897,21 @@ export function NoteInput() {
|
||||
isArchived && "text-yellow-600"
|
||||
)}
|
||||
onClick={() => setIsArchived(!isArchived)}
|
||||
title="Archive"
|
||||
title={t('notes.archive')}
|
||||
>
|
||||
<Archive className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{isArchived ? 'Unarchive' : 'Archive'}</TooltipContent>
|
||||
<TooltipContent>{isArchived ? t('notes.unarchive') : t('notes.archive')}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8" title="More">
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8" title={t('notes.more')}>
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>More</TooltipContent>
|
||||
<TooltipContent>{t('notes.more')}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
@@ -756,7 +926,7 @@ export function NoteInput() {
|
||||
<Undo2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Undo (Ctrl+Z)</TooltipContent>
|
||||
<TooltipContent>{t('notes.undoShortcut')}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
@@ -771,7 +941,7 @@ export function NoteInput() {
|
||||
<Redo2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Redo (Ctrl+Y)</TooltipContent>
|
||||
<TooltipContent>{t('notes.redoShortcut')}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
@@ -782,14 +952,14 @@ export function NoteInput() {
|
||||
disabled={isSubmitting}
|
||||
size="sm"
|
||||
>
|
||||
{isSubmitting ? 'Adding...' : 'Add'}
|
||||
{isSubmitting ? t('notes.adding') : t('notes.add')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={handleClose}
|
||||
size="sm"
|
||||
>
|
||||
Close
|
||||
{t('general.close')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -831,12 +1001,12 @@ export function NoteInput() {
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Set Reminder</DialogTitle>
|
||||
<DialogTitle>{t('notes.setReminder')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="reminder-date" className="text-sm font-medium">
|
||||
Date
|
||||
{t('notes.date')}
|
||||
</label>
|
||||
<Input
|
||||
id="reminder-date"
|
||||
@@ -848,7 +1018,7 @@ export function NoteInput() {
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label htmlFor="reminder-time" className="text-sm font-medium">
|
||||
Time
|
||||
{t('notes.time')}
|
||||
</label>
|
||||
<Input
|
||||
id="reminder-time"
|
||||
@@ -861,10 +1031,10 @@ export function NoteInput() {
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="ghost" onClick={() => setShowReminderDialog(false)}>
|
||||
Cancel
|
||||
{t('general.cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleReminderSave}>
|
||||
Set Reminder
|
||||
{t('notes.setReminderButton')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
@@ -897,7 +1067,7 @@ export function NoteInput() {
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add Link</DialogTitle>
|
||||
<DialogTitle>{t('notes.addLink')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<Input
|
||||
@@ -915,10 +1085,10 @@ export function NoteInput() {
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="ghost" onClick={() => setShowLinkDialog(false)}>
|
||||
Cancel
|
||||
{t('general.cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleAddLink}>
|
||||
Add
|
||||
{t('general.add')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
Reference in New Issue
Block a user