experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(extension): add sync status bar to popup with manual sync button

+177 -8
+94 -7
backend/extension/popup.html
··· 13 13 14 14 html, body { 15 15 width: 320px; 16 - height: 48px; 16 + height: 72px; 17 17 background-color: rgba(40, 44, 52, 0.95); 18 18 font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif; 19 19 color: white; ··· 24 24 width: 100%; 25 25 height: 100%; 26 26 display: flex; 27 + flex-direction: column; 28 + justify-content: center; 29 + } 30 + 31 + .command-row { 32 + display: flex; 27 33 align-items: center; 28 34 padding: 0 12px; 35 + height: 40px; 29 36 } 30 37 31 38 .command-display { ··· 118 125 .command-display.hidden { 119 126 display: none; 120 127 } 128 + 129 + /* Status bar */ 130 + .status-bar { 131 + display: flex; 132 + align-items: center; 133 + justify-content: space-between; 134 + padding: 0 12px 8px; 135 + font-size: 11px; 136 + color: rgba(255, 255, 255, 0.5); 137 + } 138 + 139 + .sync-status { 140 + display: flex; 141 + align-items: center; 142 + gap: 6px; 143 + } 144 + 145 + .sync-status.warning { 146 + color: #fbbf24; 147 + } 148 + 149 + .sync-status.syncing { 150 + color: #60a5fa; 151 + } 152 + 153 + .sync-dot { 154 + width: 6px; 155 + height: 6px; 156 + border-radius: 50%; 157 + background: currentColor; 158 + } 159 + 160 + .sync-status.syncing .sync-dot { 161 + animation: pulse 1s ease-in-out infinite; 162 + } 163 + 164 + @keyframes pulse { 165 + 0%, 100% { opacity: 1; } 166 + 50% { opacity: 0.4; } 167 + } 168 + 169 + .sync-btn { 170 + background: none; 171 + border: none; 172 + color: rgba(255, 255, 255, 0.5); 173 + cursor: pointer; 174 + padding: 2px 6px; 175 + font-size: 11px; 176 + border-radius: 3px; 177 + transition: background 0.15s, color 0.15s; 178 + } 179 + 180 + .sync-btn:hover { 181 + background: rgba(255, 255, 255, 0.1); 182 + color: rgba(255, 255, 255, 0.8); 183 + } 184 + 185 + .sync-btn:disabled { 186 + cursor: default; 187 + opacity: 0.5; 188 + } 189 + 190 + .pending-badge { 191 + background: rgba(251, 191, 36, 0.2); 192 + color: #fbbf24; 193 + padding: 1px 5px; 194 + border-radius: 8px; 195 + font-size: 10px; 196 + margin-left: 4px; 197 + } 121 198 </style> 122 199 </head> 123 200 <body> 124 201 <div class="center-wrapper"> 125 - <div class="command-display" id="command-display"> 126 - <input id="command-input" type="text" autofocus spellcheck="false" /> 127 - <div id="command-text" class="placeholder">tag, note, or search...</div> 202 + <div class="command-row"> 203 + <div class="command-display" id="command-display"> 204 + <input id="command-input" type="text" autofocus spellcheck="false" /> 205 + <div id="command-text" class="placeholder">tag, note, or search...</div> 206 + </div> 207 + <div class="feedback" id="feedback"> 208 + <span class="feedback-icon" id="feedback-icon"></span> 209 + <span class="feedback-text" id="feedback-text"></span> 210 + </div> 128 211 </div> 129 - <div class="feedback" id="feedback"> 130 - <span class="feedback-icon" id="feedback-icon"></span> 131 - <span class="feedback-text" id="feedback-text"></span> 212 + <div class="status-bar"> 213 + <div class="sync-status" id="sync-status"> 214 + <span class="sync-dot"></span> 215 + <span id="sync-text">Loading...</span> 216 + <span class="pending-badge" id="pending-badge" style="display: none;"></span> 217 + </div> 218 + <button class="sync-btn" id="sync-btn" title="Sync now">Sync</button> 132 219 </div> 133 220 </div> 134 221
+83
backend/extension/popup.js
··· 18 18 const feedbackEl = document.getElementById('feedback'); 19 19 const feedbackIcon = document.getElementById('feedback-icon'); 20 20 const feedbackText = document.getElementById('feedback-text'); 21 + const syncStatusEl = document.getElementById('sync-status'); 22 + const syncTextEl = document.getElementById('sync-text'); 23 + const pendingBadge = document.getElementById('pending-badge'); 24 + const syncBtn = document.getElementById('sync-btn'); 21 25 22 26 async function init() { 23 27 try { ··· 30 34 commandInput.addEventListener('input', onInput); 31 35 commandInput.addEventListener('keydown', onKeyDown); 32 36 commandInput.focus(); 37 + 38 + // Load sync status 39 + loadSyncStatus(); 40 + 41 + // Sync button handler 42 + syncBtn.addEventListener('click', triggerSync); 33 43 } 34 44 35 45 function onInput(e) { ··· 167 177 }).then(response => { 168 178 console.log('Search results:', response); 169 179 }).catch(err => console.error('Search failed:', err)); 180 + } 181 + 182 + // ==================== Sync Status ==================== 183 + 184 + async function loadSyncStatus() { 185 + try { 186 + const response = await chrome.runtime.sendMessage({ type: 'get-diagnostics' }); 187 + 188 + if (!response?.success) { 189 + updateSyncDisplay('warning', 'Error loading status'); 190 + return; 191 + } 192 + 193 + const { syncStatus } = response.data; 194 + 195 + if (!syncStatus?.configured) { 196 + updateSyncDisplay('warning', 'Not configured'); 197 + syncBtn.style.display = 'none'; 198 + return; 199 + } 200 + 201 + // Show pending count if any 202 + if (syncStatus.pendingCount > 0) { 203 + pendingBadge.textContent = syncStatus.pendingCount; 204 + pendingBadge.style.display = 'inline'; 205 + } 206 + 207 + // Format last sync time 208 + if (syncStatus.lastSyncTime) { 209 + const ago = formatTimeAgo(new Date(syncStatus.lastSyncTime)); 210 + updateSyncDisplay('', `Synced ${ago}`); 211 + } else { 212 + updateSyncDisplay('warning', 'Never synced'); 213 + } 214 + } catch (err) { 215 + console.error('Failed to load sync status:', err); 216 + updateSyncDisplay('warning', 'Error'); 217 + } 218 + } 219 + 220 + function updateSyncDisplay(statusClass, text) { 221 + syncStatusEl.className = 'sync-status' + (statusClass ? ` ${statusClass}` : ''); 222 + syncTextEl.textContent = text; 223 + } 224 + 225 + function formatTimeAgo(date) { 226 + const seconds = Math.floor((Date.now() - date.getTime()) / 1000); 227 + 228 + if (seconds < 60) return 'just now'; 229 + if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`; 230 + if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`; 231 + return `${Math.floor(seconds / 86400)}d ago`; 232 + } 233 + 234 + async function triggerSync() { 235 + syncBtn.disabled = true; 236 + updateSyncDisplay('syncing', 'Syncing...'); 237 + 238 + try { 239 + const response = await chrome.runtime.sendMessage({ type: 'sync-now' }); 240 + 241 + if (response?.success) { 242 + pendingBadge.style.display = 'none'; 243 + updateSyncDisplay('', 'Synced just now'); 244 + } else { 245 + updateSyncDisplay('warning', response?.error || 'Sync failed'); 246 + } 247 + } catch (err) { 248 + console.error('Sync failed:', err); 249 + updateSyncDisplay('warning', 'Sync failed'); 250 + } finally { 251 + syncBtn.disabled = false; 252 + } 170 253 } 171 254 172 255 init();
-1
backend/tauri-mobile/src-tauri/gen/apple/assets
··· 1 - ../../../dist