Merge pull request #235 from gamosoft/features/autosave-delay-config

configurable autosave
This commit is contained in:
Guillermo Villar 2026-06-08 11:02:28 +02:00 committed by GitHub
commit e715639a43
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 51 additions and 4 deletions

View File

@ -230,6 +230,16 @@ UPLOAD_MAX_AUDIO_MB = int(os.getenv('UPLOAD_MAX_AUDIO_MB', '50'))
UPLOAD_MAX_VIDEO_MB = int(os.getenv('UPLOAD_MAX_VIDEO_MB', '100')) UPLOAD_MAX_VIDEO_MB = int(os.getenv('UPLOAD_MAX_VIDEO_MB', '100'))
UPLOAD_MAX_PDF_MB = int(os.getenv('UPLOAD_MAX_PDF_MB', '20')) UPLOAD_MAX_PDF_MB = int(os.getenv('UPLOAD_MAX_PDF_MB', '20'))
# Autosave debounce in milliseconds (applies to note typing AND drawing PNG autosave).
try:
_autosave_raw = int(os.getenv(
'AUTOSAVE_DELAY_MS',
str(config.get('ui', {}).get('autosave_delay_ms', 1000))
))
except (TypeError, ValueError):
_autosave_raw = 1000
AUTOSAVE_DELAY_MS = max(250, min(60000, _autosave_raw))
if DEMO_MODE: if DEMO_MODE:
# Enable rate limiting for demo deployments # Enable rate limiting for demo deployments
limiter = Limiter(key_func=get_remote_address, default_limits=["200/hour"]) limiter = Limiter(key_func=get_remote_address, default_limits=["200/hour"])
@ -488,6 +498,7 @@ async def get_config():
"searchEnabled": config['search']['enabled'], "searchEnabled": config['search']['enabled'],
"demoMode": DEMO_MODE, # Expose demo mode flag to frontend "demoMode": DEMO_MODE, # Expose demo mode flag to frontend
"alreadyDonated": ALREADY_DONATED, # Hide support buttons if true "alreadyDonated": ALREADY_DONATED, # Hide support buttons if true
"autosaveDelayMs": AUTOSAVE_DELAY_MS, # Debounce for note/drawing autosave
"authentication": { "authentication": {
"enabled": config.get('authentication', {}).get('enabled', False) "enabled": config.get('authentication', {}).get('enabled', False)
} }

View File

@ -22,7 +22,14 @@ storage:
search: search:
enabled: true enabled: true
# User interface behavior
ui:
# Autosave debounce in milliseconds (applies to note typing and drawing autosave).
# Lower = saves more often (more disk writes); higher = saves less often.
# Override via the AUTOSAVE_DELAY_MS env var. Server clamps to 250-60000ms.
autosave_delay_ms: 1000
authentication: authentication:
# Authentication settings # Authentication settings
# Set enabled to true to require login # Set enabled to true to require login

View File

@ -67,6 +67,30 @@ environment:
- UPLOAD_MAX_VIDEO_MB=500 - UPLOAD_MAX_VIDEO_MB=500
``` ```
### User Interface
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `AUTOSAVE_DELAY_MS` | integer | `1000` | Autosave debounce in milliseconds (applies to note typing and drawing autosave). Server-clamped to 25060000ms. |
#### Example: Slower autosave for very large notes
```bash
# Docker
docker run -e AUTOSAVE_DELAY_MS=5000 ...
# Docker Compose
environment:
- AUTOSAVE_DELAY_MS=5000
```
Equivalent in `config.yaml`:
```yaml
ui:
autosave_delay_ms: 5000
```
## 🎯 Configuration Priority ## 🎯 Configuration Priority
Configuration is loaded in this order (later overrides earlier): Configuration is loaded in this order (later overrides earlier):

View File

@ -2,7 +2,7 @@
// Configuration constants // Configuration constants
const CONFIG = { const CONFIG = {
AUTOSAVE_DELAY: 1000, // ms - Debounce before note save (autoSave) and drawing PNG autosave (_drawingScheduleAutosave) AUTOSAVE_DELAY: 1000, // ms - Fallback only; runtime value lives on this.autosaveDelayMs (hydrated from /api/config). Used by autoSave() and _drawingScheduleAutosave().
/** Must match drawingRedraw() fill and eraser stroke color (opaque “whiteboard”). */ /** Must match drawingRedraw() fill and eraser stroke color (opaque “whiteboard”). */
DRAWING_BACKGROUND: '#ffffff', DRAWING_BACKGROUND: '#ffffff',
/** /**
@ -260,6 +260,7 @@ function noteApp() {
authEnabled: false, authEnabled: false,
demoMode: false, demoMode: false,
alreadyDonated: false, alreadyDonated: false,
autosaveDelayMs: CONFIG.AUTOSAVE_DELAY, // hydrated from /api/config in loadConfig()
notes: [], notes: [],
currentNote: '', currentNote: '',
currentNoteName: '', currentNoteName: '',
@ -959,6 +960,10 @@ function noteApp() {
this.authEnabled = config.authentication?.enabled || false; this.authEnabled = config.authentication?.enabled || false;
this.demoMode = config.demoMode || false; this.demoMode = config.demoMode || false;
this.alreadyDonated = config.alreadyDonated || false; this.alreadyDonated = config.alreadyDonated || false;
// Server already clamps autosaveDelayMs to a sane range; we just sanity-check the type.
if (Number.isFinite(config.autosaveDelayMs) && config.autosaveDelayMs > 0) {
this.autosaveDelayMs = config.autosaveDelayMs;
}
} catch (error) { } catch (error) {
console.error('Failed to load config:', error); console.error('Failed to load config:', error);
} }
@ -3253,7 +3258,7 @@ function noteApp() {
this._drawingAutosaveTimeout = null; this._drawingAutosaveTimeout = null;
this.drawingSave(); this.drawingSave();
}; };
this._drawingAutosaveTimeout = setTimeout(attemptSave, CONFIG.AUTOSAVE_DELAY); this._drawingAutosaveTimeout = setTimeout(attemptSave, this.autosaveDelayMs);
}, },
/** /**
@ -5064,7 +5069,7 @@ function noteApp() {
this.commitToHistory(); this.commitToHistory();
} }
this.saveNote(); this.saveNote();
}, CONFIG.AUTOSAVE_DELAY); }, this.autosaveDelayMs);
}, },
// Mark that we have pending changes (called on each keystroke) // Mark that we have pending changes (called on each keystroke)