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>
120 lines
4.4 KiB
Plaintext
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>
|