Files
Randify.pro/src/components/dm/NotesPanel.astro
T
emilandClaude Opus 4.7 c01c34d3e0 style(dm): widen desktop dashboard at xl/2xl breakpoints
Bump container max-width and right context column at xl/2xl so the
reference and notes panels get noticeably more room on larger monitors.
Mobile and lg layouts are unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 18:23:35 +03:00

120 lines
4.4 KiB
Plaintext

---
import DmCard from './DmCard.astro';
import DmButton from './DmButton.astro';
import { dmTranslations as T } from '../../i18n/dm-translations';
---
<DmCard padding="md" dataTestid="notes-section">
<textarea
id="notes-textarea"
class="w-full h-64 xl:h-80 2xl:h-96 bg-[var(--bg-input)] border border-[var(--border-color)] rounded-lg p-4 text-[var(--text-primary)] placeholder:text-[var(--text-muted)] focus:border-[var(--gold)] focus:ring-1 focus:ring-[var(--gold)] transition-all resize-none"
placeholder={T.emptyNotesPlaceholder}
data-testid="notes-textarea"
></textarea>
<div class="mt-2 flex items-center justify-between">
<span id="notes-saved-indicator" class="text-xs text-[var(--text-muted)] opacity-0 transition-opacity duration-300" data-testid="notes-saved-indicator">
{T.actionSaved}
</span>
<span id="notes-char-count" class="text-xs text-[var(--text-muted)]" data-testid="notes-char-count">0</span>
</div>
<div class="mt-3 flex items-center justify-end gap-2">
<DmButton id="notes-clear-btn" variant="ghost" size="sm" dataTestid="notes-clear-btn">{T.actionClear}</DmButton>
<DmButton id="notes-copy-btn" variant="secondary" size="sm">{T.actionCopy}</DmButton>
</div>
</DmCard>
<script>
import { saveNotes, loadNotes, clearNotes } from '@/lib/client/notes';
function initNotesPanel(container: HTMLElement) {
const textarea = container.querySelector('[data-testid="notes-textarea"]') as HTMLTextAreaElement;
const savedIndicator = container.querySelector('[data-testid="notes-saved-indicator"]') as HTMLSpanElement;
const charCount = container.querySelector('[data-testid="notes-char-count"]') as HTMLSpanElement;
const clearBtn = container.querySelector('[data-testid="notes-clear-btn"]') as HTMLButtonElement;
const copyBtn = container.querySelector('#notes-copy-btn') as HTMLButtonElement;
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
let hideIndicatorTimer: ReturnType<typeof setTimeout> | null = null;
function formatTime(date: Date): string {
return date.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' });
}
function showSavedIndicator(message?: string) {
const text = message || `Сохранено · ${formatTime(new Date())}`;
savedIndicator.textContent = text;
savedIndicator.classList.remove('opacity-0');
if (hideIndicatorTimer) clearTimeout(hideIndicatorTimer);
hideIndicatorTimer = setTimeout(() => {
savedIndicator.classList.add('opacity-0');
}, 1500);
}
function updateCharCount() {
const count = textarea.value.length;
charCount.textContent = String(count);
}
function loadSavedNotes() {
const saved = loadNotes();
textarea.value = saved;
updateCharCount();
}
function handleInput() {
updateCharCount();
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
saveNotes(textarea.value);
showSavedIndicator();
}, 500);
}
function handleClear() {
textarea.value = '';
clearNotes();
updateCharCount();
savedIndicator.classList.add('opacity-0');
if (hideIndicatorTimer) clearTimeout(hideIndicatorTimer);
if (debounceTimer) clearTimeout(debounceTimer);
textarea.focus();
}
async function handleCopy() {
const text = textarea.value;
if (!text) return;
try {
await navigator.clipboard.writeText(text);
const originalText = copyBtn.textContent || 'Скопировать';
copyBtn.textContent = 'Скопировано!';
setTimeout(() => {
copyBtn.textContent = originalText;
}, 1500);
} catch {
// Silently fail if clipboard is unavailable
}
}
function handleStorageChange(e: StorageEvent) {
if (e.key === 'dm-notes' && e.newValue !== null) {
textarea.value = e.newValue;
updateCharCount();
showSavedIndicator('Обновлено');
}
}
textarea.addEventListener('input', handleInput);
clearBtn.addEventListener('click', handleClear);
copyBtn.addEventListener('click', handleCopy);
window.addEventListener('storage', handleStorageChange);
loadSavedNotes();
}
document.querySelectorAll('[data-testid="notes-section"]').forEach((el) => {
initNotesPanel(el as HTMLElement);
});
</script>