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();
};