diff --git a/static/app.js b/static/app.js index b398720..3f374b0 100644 --- a/static/app.js +++ b/static/app.js @@ -59,7 +59,6 @@ function enableEnterToSubmit(input, form) { } }); }); -}); } // Initialize on DOM ready @@ -283,3 +282,92 @@ document.addEventListener('DOMContentLoaded', () => { }); }); }); + +// ============================================ +// Draft Auto-Save Functions +// ============================================ + +// Save draft to localStorage +function saveDraft(key, content) { + try { + localStorage.setItem(key, content); + localStorage.setItem(key + '_timestamp', Date.now().toString()); + } catch (e) { + console.error('Failed to save draft:', e); + } +} + +// Load draft from localStorage +function loadDraft(key) { + try { + return localStorage.getItem(key); + } catch (e) { + console.error('Failed to load draft:', e); + return null; + } +} + +// Clear draft from localStorage +function clearDraft(key) { + try { + localStorage.removeItem(key); + localStorage.removeItem(key + '_timestamp'); + } catch (e) { + console.error('Failed to clear draft:', e); + } +} + +// Get draft timestamp +function getDraftTimestamp(key) { + try { + const timestamp = localStorage.getItem(key + '_timestamp'); + return timestamp ? parseInt(timestamp) : null; + } catch (e) { + console.error('Failed to get draft timestamp:', e); + return null; + } +} + +// Format time ago +function formatTimeAgo(timestamp) { + const now = Date.now(); + const diff = now - timestamp; + const seconds = Math.floor(diff / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + if (days > 0) return `${days} day${days > 1 ? 's' : ''} ago`; + if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''} ago`; + if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`; + return 'just now'; +} + +// Show draft restoration indicator +function showDraftIndicator(content, timestamp, onRestore, onDiscard) { + const indicator = document.createElement('div'); + indicator.className = 'draft-indicator'; + indicator.innerHTML = ` +
+ 📝 + Draft from ${formatTimeAgo(timestamp)} + + +
+ `; + + const restoreBtn = indicator.querySelector('.restore'); + const discardBtn = indicator.querySelector('.discard'); + + restoreBtn.addEventListener('click', () => { + onRestore(content); + indicator.remove(); + }); + + discardBtn.addEventListener('click', () => { + onDiscard(); + indicator.remove(); + }); + + return indicator; +} diff --git a/static/style.css b/static/style.css index 51dfd29..aef959a 100644 --- a/static/style.css +++ b/static/style.css @@ -687,6 +687,70 @@ input.error, textarea.error, select.error { font-weight: bold; } +/* Draft indicator styles */ +.draft-indicator { + background-color: #f3d2c1; + border: 1px solid #001858; + border-radius: 5px; + padding: 10px; + margin-bottom: 10px; + animation: slideInFromTop 0.3s ease-out; +} + +.draft-indicator-content { + display: flex; + align-items: center; + gap: 10px; + font-family: monospace; +} + +.draft-indicator-icon { + font-size: 1.2em; +} + +.draft-indicator-text { + flex: 1; + color: #001858; + font-size: 0.9em; +} + +.draft-indicator-button { + padding: 5px 12px; + border: 1px solid #001858; + background-color: #fef6e4; + color: #001858; + border-radius: 3px; + cursor: pointer; + font-family: monospace; + font-size: 0.85em; +} + +.draft-indicator-button:hover { + background-color: #001858; + color: #fef6e4; +} + +.draft-indicator-button.restore { + background-color: #8bd3dd; + border-color: #001858; +} + +.draft-indicator-button.restore:hover { + background-color: #001858; + color: #8bd3dd; +} + +@keyframes slideInFromTop { + from { + transform: translateY(-20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + @media (prefers-color-scheme: dark) { .spinner { border-color: #444; @@ -724,6 +788,37 @@ input.error, textarea.error, select.error { .char-counter { color: #fef6e4; } + + .draft-indicator { + background-color: #2a2a2a; + border-color: #fef6e4; + } + + .draft-indicator-text { + color: #fef6e4; + } + + .draft-indicator-button { + background-color: #1a1a1a; + color: #fef6e4; + border-color: #fef6e4; + } + + .draft-indicator-button:hover { + background-color: #fef6e4; + color: #1a1a1a; + } + + .draft-indicator-button.restore { + background-color: #8bd3dd; + color: #001858; + border-color: #8bd3dd; + } + + .draft-indicator-button.restore:hover { + background-color: #fef6e4; + color: #001858; + } } @media (max-width: 600px) { diff --git a/templates/pages/chat.html b/templates/pages/chat.html index 1e92f12..2adba73 100644 --- a/templates/pages/chat.html +++ b/templates/pages/chat.html @@ -851,6 +851,61 @@ } }); + // Draft auto-save functionality + const draftKey = 'draft_chat_{{.Board.ID}}'; + let draftSaveTimeout; + + // Load existing draft on page load + const existingDraft = loadDraft(draftKey); + const draftTimestamp = getDraftTimestamp(draftKey); + if (existingDraft && draftTimestamp) { + // Check if draft is less than 7 days old + const sevenDaysAgo = Date.now() - (7 * 24 * 60 * 60 * 1000); + if (draftTimestamp > sevenDaysAgo) { + const indicator = showDraftIndicator( + existingDraft, + draftTimestamp, + (content) => { + chatInput.value = content; + chatInput.focus(); + }, + () => { + clearDraft(draftKey); + } + ); + // Insert indicator before chat input + const chatInputContainer = document.querySelector('.chat-input'); + chatInputContainer.parentNode.insertBefore(indicator, chatInputContainer); + } else { + // Draft is too old, clear it + clearDraft(draftKey); + } + } + + // Save draft on input (debounced) + chatInput.addEventListener('input', () => { + clearTimeout(draftSaveTimeout); + draftSaveTimeout = setTimeout(() => { + const content = chatInput.value.trim(); + if (content) { + saveDraft(draftKey, content); + } else { + clearDraft(draftKey); + } + }, 2000); // Save after 2 seconds of inactivity + }); + + // Clear draft after successful message send + const originalSendMessage = window.sendMessage; + window.sendMessage = function() { + const input = document.getElementById('chat-input-text'); + const content = input.value.trim(); + if (content !== '') { + clearDraft(draftKey); + } + originalSendMessage(); + }; + // Initial check for scroll position checkScrollPosition(); };