added app

This commit is contained in:
Gamosoft 2026-01-01 15:26:03 +01:00
parent 6114ae76b2
commit 2912e41374
6 changed files with 161 additions and 1 deletions

View File

@ -3,7 +3,7 @@ NoteDiscovery - Self-Hosted Markdown Knowledge Base
Main FastAPI application
"""
from fastapi import FastAPI, HTTPException, UploadFile, File, Request, Form, Depends, APIRouter
from fastapi import FastAPI, HTTPException, UploadFile, File, Request, Form, Depends, APIRouter, Response
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse, RedirectResponse
from fastapi.middleware.cors import CORSMiddleware
@ -171,6 +171,21 @@ plugin_manager.run_hook('on_app_startup')
static_path = Path(__file__).parent.parent / "frontend"
app.mount("/static", StaticFiles(directory=static_path), name="static")
# PWA Service Worker - must be served from root for proper scope
@app.get("/sw.js")
@limiter.limit("30/minute")
async def service_worker(request: Request):
"""Serve the PWA service worker from root path for proper scope.
Injects the app version from VERSION file for cache invalidation."""
sw_path = static_path / "sw.js"
if sw_path.exists():
async with aiofiles.open(sw_path, 'r', encoding='utf-8') as f:
content = await f.read()
# Inject app version into cache name
content = content.replace('__APP_VERSION__', version)
return Response(content=content, media_type="application/javascript")
raise HTTPException(status_code=404, detail="Service worker not found")
# ============================================================================
# Custom Exception Handlers

View File

@ -875,6 +875,15 @@ function noteApp() {
if (this.showGraph) {
setTimeout(() => this.initGraph(), 300);
}
// Update PWA theme-color meta tag to match current theme
const themeColorMeta = document.querySelector('meta[name="theme-color"]');
if (themeColorMeta) {
// Get the accent color from CSS variables
const accentColor = getComputedStyle(document.documentElement)
.getPropertyValue('--accent-primary').trim() || '#667eea';
themeColorMeta.setAttribute('content', accentColor);
}
} catch (error) {
console.error('Failed to load theme:', error);
}

View File

@ -11,6 +11,20 @@
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon.svg">
<link rel="apple-touch-icon" href="/static/logo.svg">
<!-- PWA Manifest -->
<link rel="manifest" href="/static/manifest.json">
<!-- PWA Meta Tags -->
<meta name="application-name" content="NoteDiscovery">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="NoteDiscovery">
<meta name="mobile-web-app-capable" content="yes">
<!-- Theme color: initial value, updated dynamically by app.js when theme changes -->
<meta name="theme-color" content="#667eea">
<meta name="msapplication-TileColor" content="#667eea">
<meta name="msapplication-tap-highlight" content="no">
<!-- Tailwind CSS from CDN (v3.4.17) -->
<script src="https://cdn.tailwindcss.com/3.4.17"></script>
@ -2816,6 +2830,21 @@
</nav>
<script src="/static/app.js"></script>
<!-- PWA Service Worker Registration -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then((registration) => {
console.log('SW registered:', registration.scope);
})
.catch((error) => {
console.log('SW registration failed:', error);
});
});
}
</script>
</body>
</html>

View File

@ -6,6 +6,13 @@
<title>Login - NoteDiscovery</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<!-- PWA Manifest -->
<link rel="manifest" href="/static/manifest.json">
<meta name="theme-color" content="#667eea">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="NoteDiscovery">
<!-- Preload translations (same approach as index.html) -->
<script>
(function() {

33
frontend/manifest.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "NoteDiscovery",
"short_name": "Notes",
"description": "A beautiful, self-hosted markdown notes application",
"start_url": "/",
"display": "standalone",
"background_color": "#0f172a",
"theme_color": "#667eea",
"orientation": "any",
"scope": "/",
"icons": [
{
"src": "/static/logo.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/static/logo.svg",
"sizes": "192x192",
"type": "image/svg+xml",
"purpose": "any maskable"
},
{
"src": "/static/logo.svg",
"sizes": "512x512",
"type": "image/svg+xml",
"purpose": "any maskable"
}
],
"categories": ["productivity", "utilities"]
}

67
frontend/sw.js Normal file
View File

@ -0,0 +1,67 @@
// NoteDiscovery Service Worker
// Minimal service worker for PWA install support
// Cache version - automatically uses app version from VERSION file
// Cache is invalidated when app version changes (e.g., 0.10.4 -> 0.10.5)
// This forces users to download fresh files when you release a new version.
const CACHE_NAME = 'notediscovery-__APP_VERSION__';
// Assets to cache for faster repeat visits
const PRECACHE_ASSETS = [
'/static/logo.svg',
'/static/favicon.svg',
'/static/app.js'
];
// Install event - cache essential assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(PRECACHE_ASSETS))
.then(() => self.skipWaiting())
);
});
// Activate event - clean up old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name))
);
}).then(() => self.clients.claim())
);
});
// Fetch event - network first, fallback to cache for assets
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
// Only handle same-origin requests
if (url.origin !== location.origin) {
return;
}
// For API calls, always go to network
if (url.pathname.startsWith('/api/')) {
return;
}
// For static assets, try cache first then network
if (url.pathname.startsWith('/static/')) {
event.respondWith(
caches.match(event.request)
.then((cached) => cached || fetch(event.request))
);
return;
}
// For everything else, network first
event.respondWith(
fetch(event.request)
.catch(() => caches.match(event.request))
);
});