| <!-- src/main/resources/static/offline.html --> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> | |
| <title>You're Offline - IE Research Platform</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: white; | |
| } | |
| .offline-container { | |
| text-align: center; | |
| padding: 2rem; | |
| max-width: 500px; | |
| } | |
| .offline-icon { | |
| font-size: 5rem; | |
| margin-bottom: 1rem; | |
| } | |
| h1 { | |
| font-size: 2rem; | |
| margin-bottom: 1rem; | |
| } | |
| p { | |
| margin-bottom: 1.5rem; | |
| line-height: 1.6; | |
| opacity: 0.9; | |
| } | |
| .saved-articles { | |
| background: rgba(255,255,255,0.1); | |
| border-radius: 12px; | |
| padding: 1rem; | |
| margin: 1.5rem 0; | |
| text-align: left; | |
| } | |
| .saved-articles h3 { | |
| margin-bottom: 0.5rem; | |
| font-size: 1rem; | |
| } | |
| .saved-articles ul { | |
| list-style: none; | |
| } | |
| .saved-articles li { | |
| padding: 0.5rem 0; | |
| border-bottom: 1px solid rgba(255,255,255,0.2); | |
| } | |
| .saved-articles li a { | |
| color: white; | |
| text-decoration: none; | |
| } | |
| button { | |
| background: white; | |
| color: #667eea; | |
| border: none; | |
| padding: 12px 24px; | |
| border-radius: 25px; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| margin: 0.5rem; | |
| transition: transform 0.2s; | |
| } | |
| button:hover { | |
| transform: translateY(-2px); | |
| } | |
| .retry-button { | |
| background: rgba(255,255,255,0.2); | |
| color: white; | |
| } | |
| .loading { | |
| display: inline-block; | |
| width: 20px; | |
| height: 20px; | |
| border: 2px solid #fff; | |
| border-radius: 50%; | |
| border-top-color: transparent; | |
| animation: spin 1s linear infinite; | |
| margin-left: 10px; | |
| vertical-align: middle; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="offline-container"> | |
| <div class="offline-icon">๐ก</div> | |
| <h1>You're Offline</h1> | |
| <p>It looks like you've lost your internet connection. Don't worry! You can still access your saved content and continue working offline.</p> | |
| <div class="saved-articles" id="saved-articles"> | |
| <h3>๐ Available Offline Content</h3> | |
| <ul id="offline-list"> | |
| <li>Loading saved articles...</li> | |
| </ul> | |
| </div> | |
| <div> | |
| <button onclick="retryConnection()" class="retry-button" id="retry-btn"> | |
| ๐ Try to Reconnect | |
| <span class="loading" style="display:none"></span> | |
| </button> | |
| <button onclick="goHome()">๐ Go to Home</button> | |
| </div> | |
| </div> | |
| <script> | |
| // Load saved articles from IndexedDB | |
| let db; | |
| function initDB() { | |
| const request = indexedDB.open('ResearchPlatform', 1); | |
| request.onerror = (event) => { | |
| console.error('IndexedDB error:', event); | |
| document.getElementById('offline-list').innerHTML = '<li>No saved articles found</li>'; | |
| }; | |
| request.onsuccess = (event) => { | |
| db = event.target.result; | |
| loadSavedArticles(); | |
| }; | |
| request.onupgradeneeded = (event) => { | |
| const db = event.target.result; | |
| if (!db.objectStoreNames.contains('offlineArticles')) { | |
| db.createObjectStore('offlineArticles', { keyPath: 'id' }); | |
| } | |
| if (!db.objectStoreNames.contains('pendingActions')) { | |
| db.createObjectStore('pendingActions', { keyPath: 'id', autoIncrement: true }); | |
| } | |
| }; | |
| } | |
| function loadSavedArticles() { | |
| if (!db) { | |
| initDB(); | |
| return; | |
| } | |
| const transaction = db.transaction(['offlineArticles'], 'readonly'); | |
| const store = transaction.objectStore('offlineArticles'); | |
| const request = store.getAll(); | |
| request.onsuccess = () => { | |
| const articles = request.result; | |
| const list = document.getElementById('offline-list'); | |
| if (articles.length === 0) { | |
| list.innerHTML = '<li>No saved articles found. Save some articles for offline reading!</li>'; | |
| return; | |
| } | |
| list.innerHTML = articles.map(article => ` | |
| <li> | |
| <a href="/article/${article.id}" onclick="return false;"> | |
| ๐ ${article.title} | |
| </a> | |
| </li> | |
| `).join(''); | |
| }; | |
| request.onerror = () => { | |
| document.getElementById('offline-list').innerHTML = '<li>Error loading saved articles</li>'; | |
| }; | |
| } | |
| function retryConnection() { | |
| const btn = document.getElementById('retry-btn'); | |
| const originalText = btn.innerHTML; | |
| btn.innerHTML = '๐ Checking connection... <span class="loading"></span>'; | |
| btn.disabled = true; | |
| fetch('/api/health') | |
| .then(response => { | |
| if (response.ok) { | |
| window.location.reload(); | |
| } else { | |
| throw new Error('Server not responding'); | |
| } | |
| }) | |
| .catch(() => { | |
| btn.innerHTML = originalText; | |
| btn.disabled = false; | |
| alert('Still offline. Please check your internet connection and try again.'); | |
| }); | |
| } | |
| function goHome() { | |
| if (navigator.onLine) { | |
| window.location.href = '/'; | |
| } else { | |
| alert('You are still offline. Please connect to the internet first.'); | |
| } | |
| } | |
| // Listen for online status | |
| window.addEventListener('online', () => { | |
| document.querySelector('.offline-container').innerHTML += ` | |
| <div style="background: rgba(76, 175, 80, 0.9); padding: 10px; border-radius: 8px; margin-top: 1rem;"> | |
| โ You're back online! <a href="/" style="color: white;">Click here to refresh</a> | |
| </div> | |
| `; | |
| }); | |
| // Initialize | |
| initDB(); | |
| // Register service worker for offline capabilities | |
| if ('serviceWorker' in navigator) { | |
| navigator.serviceWorker.ready.then(registration => { | |
| // Send message to get cached content | |
| if (navigator.serviceWorker.controller) { | |
| navigator.serviceWorker.controller.postMessage({ | |
| type: 'GET_CACHED_ARTICLES' | |
| }); | |
| } | |
| }); | |
| } | |
| </script> | |
| </body> | |
| </html> |
Xet Storage Details
- Size:
- 8.1 kB
- Xet hash:
- 957b8406b91a47bf22fd5d429fd17764d532a9dbcaa071e80337dd0f78435234
ยท
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.