MetaList 0.9.0__tar.gz → 0.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {metalist-0.9.0 → metalist-0.10.0}/PKG-INFO +9 -1
- {metalist-0.9.0 → metalist-0.10.0}/README.md +8 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/note_requests.py +13 -0
- metalist-0.10.0/app/api/routes/files.py +244 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/upload_limits.py +13 -1
- metalist-0.10.0/app/db/editable_files_migration.py +50 -0
- metalist-0.10.0/app/db/file_schema.py +58 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/files_sql.py +161 -2
- {metalist-0.9.0 → metalist-0.10.0}/app/db/migrations.py +2 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/version.py +1 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/encryption_audit.py +63 -8
- {metalist-0.9.0 → metalist-0.10.0}/app/main.py +27 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/help_catalog.py +1 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-menus.md +2 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-notes.md +1 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-references.md +14 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/services/backlinks.py +2 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/services/backup_service.py +3 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/services/client_state_service.py +2 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/embedded_references.py +199 -4
- metalist-0.10.0/app/services/file_edit_sessions.py +81 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/file_registry.py +7 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/file_storage.py +403 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/services/html_export.py +9 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/services/image_size_formatting.py +1 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/services/undo_state.py +59 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/static/config/agent-menu-actions.json +6 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/config/command_palette_tags.json +5 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/static/css/main.css +515 -116
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/api-client.js +191 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/command-palette-controller.js +30 -75
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/endpoint-registry.js +8 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/note-layout-preferences.js +19 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/tag-config-loader.js +94 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/config.js +6 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/context-menu/context-menu-registry.js +78 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/context-menu/context-menu-service.js +6 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/editor-selection.js +2 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/editor-toolbar.js +2 -1
- metalist-0.10.0/app/static/js/modules/excalidraw/excalidraw-actions.js +93 -0
- metalist-0.10.0/app/static/js/modules/excalidraw/excalidraw-autosave.js +159 -0
- metalist-0.10.0/app/static/js/modules/excalidraw/excalidraw-editor-service.js +388 -0
- metalist-0.10.0/app/static/js/modules/excalidraw/excalidraw-editor-state.js +35 -0
- metalist-0.10.0/app/static/js/modules/excalidraw/excalidraw-loader.js +50 -0
- metalist-0.10.0/app/static/js/modules/excalidraw/excalidraw-preview-service.js +163 -0
- metalist-0.10.0/app/static/js/modules/excalidraw/excalidraw-scene.js +89 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/expected-errors.js +20 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/note-layout-appearance-modal.js +18 -4
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/events/context-menu-events.js +17 -2
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/events/focus-events.js +3 -2
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/events/keyboard-events.js +12 -9
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/events/mouse-events.js +41 -15
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/differential-view-service.js +23 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/floating-note-service.js +2 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/image-context-menu-action-service.js +6 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-fullscreen-service.js +2 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tag-suggestions-service.js +5 -4
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tag-syntax-service.js +28 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/excalidraw.css +1 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-Bold.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-Medium.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-Regular.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Assistant/Assistant-SemiBold.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Cascadia/CascadiaCode-Regular.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-6e066e8de2ac57ea9283adb9c24d7f0c.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-dc6a8806fa96795d7b3be5026f989a17.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/ComicShanns/ComicShanns-Regular-fcb0fc02dcbee4c9846b3e2508668039.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-349fac6ca4700ffec595a7150a0d1e1d.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-3f2c5db56cc93c5a6873b1361d730c16.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-41b173a47b57366892116a575a43e2b6.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-623ccf21b21ef6b3a0d87738f77eb071.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-b9dcf9d2e50a1eaf42fc664b50a3fd0d.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Excalifont/Excalifont-Regular-be310b9bcd4f1a43f571c46df7809174.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1-assets/fonts/Virgil/Virgil-Regular.woff2 +0 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1.LICENSE.txt +3135 -0
- metalist-0.10.0/app/static/js/vendor/excalidraw-0.18.1.min.js +208 -0
- metalist-0.10.0/app/usecases/file_edit_session.py +110 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/resize_image.py +1 -1
- {metalist-0.9.0 → metalist-0.10.0}/app/version.py +1 -1
- {metalist-0.9.0 → metalist-0.10.0}/metalist.egg-info/PKG-INFO +9 -1
- {metalist-0.9.0 → metalist-0.10.0}/metalist.egg-info/SOURCES.txt +37 -0
- metalist-0.9.0/app/api/routes/files.py +0 -95
- metalist-0.9.0/app/db/file_schema.py +0 -28
- {metalist-0.9.0 → metalist-0.10.0}/MANIFEST.in +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/__init__.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/__init__.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/deps.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/dev.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/middleware/auth.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/request_auth.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/ai.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/app_updates.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/auth.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/backups.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/notes.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/ontology.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/reminders.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/remote_images.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/routes/test.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/api/transactions.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/config.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/data_directory.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/__init__.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/backup_settings_sql.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/engine.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/file_session.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/link_titles_sql.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/live_recovery.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/notes_sql.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/ontology_rules_sql.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/reminders_sql.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/retire_sounds.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/schema.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/search_history_sql.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/session.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/settings_sql.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/db/tab_state_sql.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/exception_boundaries.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/https_proxy.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/models/commands.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/models/database.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/models/entities.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/models/enums.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/models/linked_list.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/models/list_operations.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/models/list_traversal.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/models/note_crud.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/models/utils.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/network_settings.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/presentation/__init__.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/presentation/render/__init__.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/presentation/render/note_renderer.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/presentation/templates.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/security/authenticated_logging.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/security/encryption.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/security/http_error_diagnostics.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/security/http_headers.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/security/note_html.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/security/request_boundary.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/security/request_hosts.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/security/sensitive_cache.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/security/sensitive_logging.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/security/shell_execution.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/security/validation_errors.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/server_runtime.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/__init__.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/__init__.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/actions.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/cloud_privacy.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/context.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/evidence_serialization.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/history.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/inference.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/investigation.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/judging.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/menu_actions.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/model_policy.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/openai_cost_tracking.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/openai_inference.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/permissions.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/prompt_settings.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/prompts/__init__.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/prompts/final-response.md +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/prompts/help-response.md +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/prompts/system.md +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/prompts/tool-result.md +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/retrieval_settings.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/runtime.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/scope.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skill_settings.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/__init__.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-ai.md +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-data.md +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-formatting.md +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-privacy.md +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-reminders.md +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-search.md +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/help-tags.md +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/scoped-investigation.md +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/staged-summary.md +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/tag-proposals.md +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/skills/web-browsing.md +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/staged_summary.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/structured_inference.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/tagging.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/tagging_run.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/token_estimation.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/tools.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/trace.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/web_capabilities.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/web_evidence.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/web_fetch.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/agent/web_settings.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/ai_chat.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/ai_chat_rendering.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/ai_chat_stream.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/app_update_worker.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/app_updates.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/auth.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/auth_cache_state.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/auth_service.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/backlink_index.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/backup_settings_service.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/bulk_operation.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/content_cache.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/content_formatting.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/diagnostics.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/encryption.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/exception_capture.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/footnote_layout.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/footnote_rendering.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/hierarchy.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/html_unformatting.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/hydration_state.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/inline_image_occurrences.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/input_errors.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/integrity.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/latex_rendering.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/link_titles.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/login_rate_limit.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/maintenance_mode.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/markdown_rendering.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/namespace_deletion_jobs.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/namespace_deletion_worker.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/namespace_rename_jobs.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/namespace_rename_worker.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/namespace_runtime_guard.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/namespace_switcher.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/note_fullscreen.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/note_image_tags.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/note_ordering.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/note_store.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/ontology_rules_store.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/openai_credentials.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/password_note_fields.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/public_http.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/reference_presentation.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/reminders.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/remote_image_proxy.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/request_recovery.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/resource_limits.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/root_sorting.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/runtime_generation.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/runtime_hardening.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/runtime_lock.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/search_history.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/search_history_storage.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/search_index.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/search_query.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/search_text.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/selected_text_tag.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/self_update.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/session_timeout_service.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/shell_session_service.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/snapshot.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/store.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/structured_note_renderers.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/sync.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/sync_state.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/tab_state.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/tag_case.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/tag_ontology.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/tag_rename.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/tag_suggestions.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/tag_term_matching.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/test_reset.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/tokens.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/update_backups.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/update_installer.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/update_preflight.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/view_cache.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/view_diff.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/view_state.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/services/windows_process_control.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/startup_environment.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/startup_js_sanity.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/startup_sanity.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/startup_sanity_config.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/css/floating-notes.css +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/css/forms.css +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/img/favicon.ico +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/locked.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/main.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/activity-tracker.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/agent-menu-actions.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/agent-prompt-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/agent-retrieval-settings.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/agent-web-settings.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/ai-agent-debug-view.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/ai-chat-api.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/ai-chat-message-placement.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/ai-chat-panel-controller.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/ai-chat-panel-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/ai-thinking-level-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/bulk-elapsed-clock.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/bulk-proposal-ui.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/bulk-scope-question.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/cloud-privacy-policy.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/proposal-menu.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/ai-chat/summary-batch-preview.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/api-error-diagnostics.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/api-failure-classification-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/app-update-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/application-state.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/async-result.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/auth.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/client-state-api.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/client-state-migration.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/activation-auth-policy.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/backup-command-availability.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/click-activation-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/preferences-store.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/command-palette/usage-store.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/connectivity-monitor.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/context-menu/context-menu-target-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/context-menu/input-caret-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/disconnect-editing-state-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/dom-utils-fixed.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/dom-utils.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/editor-commands.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/elapsed-time.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/error-handler.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/error-overlay.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/location-flags.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/login-namespace-picker.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/maintenance.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/agent-prompt-editor-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/ai-agent-settings-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/alphabetize-root-notes-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/backup-restore-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/backup-result-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/backup-retention-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/backup-settings-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/base-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/confirmation-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/delete-namespace-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/delete-namespace-validation.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/help-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/namespace-loading-page.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/namespace-modals.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/namespace-ports-profile.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/namespace-rename-validation.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/ontology-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/password-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/prioritize-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/random-password-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/reminder-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/reset-updated-at-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/search-suggestion-statistics-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/session-timeout-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/modals/version-info-modal.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/actions/content-actions.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/actions/history-actions.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/actions/note-actions.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/actions/search-actions.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/actions/selection-actions.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/actions/ui-actions.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/events/inactivity-events.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/events/input-events.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/events/search-events.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/mode-context.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/mode-logger.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/mode-manager-controller.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/add-style-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/animated-scroll-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/backlinks-navigation-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/backlinks-panel-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/clipboard-shortcut-policy-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/collapse-affordance-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/collapse-editing-policy-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/command-gate-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/context-menu-target-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/deselect-selection-state-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/edit-session-collapse-policy-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/edit-session-collapse-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/embedded-image-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/file-drop-policy-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/file-image-preview-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/file-reference-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/filtered-refresh-selection-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/floating-note-dom.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/floating-note-layout.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/footnote-navigation-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/history-selection-policy-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/history-shortcut-policy-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/html-paste-sanitizer-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/image-file-insert-choice-modal-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/infinite-scroll-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/link-context-menu-action-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/link-title-prefetch-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/markdown-render-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/mermaid-render-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-click-target-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-clipboard-write-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-collapse-animation-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-context-menu-action-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-delete-shortcut-policy-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-drag-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-reposition-animation-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-split-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/note-timestamp-hover-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/password-clipboard-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/polling-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/reference-source-navigation-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/remote-image-proxy-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/remove-formatting-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/root-date-separator-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/root-sort-indicator-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/root-sort-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/scroll-anchor-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/scroll-restoration-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/scroll-to-top-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-contexts-overlay-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-debounce-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-input-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-interaction-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-redaction-reveal-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-suggestion-statistics-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-suggestion-window-labels-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-suggestion-windows-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-suggestions-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/search-syntax-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/selected-formatting-removal-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/selected-text-tag-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/shell-output-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tab-dom-cache-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tab-drag-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tab-duplication-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tab-state-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tag-bar-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tag-input-selection-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/tag-proposal-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/todo-toggle-editing-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/untagged-view-indicator-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/mode-manager/services/view-mode-search-shortcut-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/note-html-sanitizer.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/password-generator.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/password-operation-response.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/password-policy.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/password-strength.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/reminder-store.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/reminder-surface-service.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/selection-markers.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/session-auth.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/tag-token.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/modules/uuid.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/namespace-deleted.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/namespace-renamed.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/dompurify-3.4.13.LICENSE.txt +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/dompurify-3.4.13.min.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/markdown-it-14.3.2.LICENSE.txt +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/markdown-it-14.3.2.min.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/mermaid-11.16.1.LICENSE.txt +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/mermaid-11.16.1.min.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/zxcvbn-4.4.2.LICENSE.txt +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/js/vendor/zxcvbn-4.4.2.js +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/note-html-policy.json +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/static/video/login-startup.mp4 +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/templates/base.html +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/templates/index.html +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/templates/locked.html +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/templates/maintenance.html +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/templates/namespace_deleted.html +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/templates/namespace_renamed.html +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/templates/notes_list.html +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/upload_limits.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/__init__.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/add_selected_text_tag.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/alphabetize_root_notes.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/base.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/bulk_tag_proposals.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/collapse.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/copy_note.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/create_child.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/create_note.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/create_sibling.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/delete_subtree.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/delete_tag.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/expand.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/indent.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/lock.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/move.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/move_to_top.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/outdent.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/paste_child.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/paste_sibling.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/prefetch_link_titles.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/prioritize.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/redo.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/rename_tag.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/reset_updated_at.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/run_shell.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/search_comment_autofill.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/search_context_tags.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/set_collapse_bulk.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/set_collapse_in_context.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/split_note.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/tag_proposals.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/toggle_reference_mode.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/toggle_todo_done.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/undo.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/unformat_content.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/update_content.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/usecases/view.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/utils/__init__.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/utils/encryption.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/app/utils/text_utils.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/convert-from-legacy.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/main.py +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/metalist.egg-info/dependency_links.txt +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/metalist.egg-info/entry_points.txt +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/metalist.egg-info/requires.txt +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/metalist.egg-info/top_level.txt +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/pyproject.toml +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/scripts/generate-lan-cert.sh +0 -0
- {metalist-0.9.0 → metalist-0.10.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: metalist
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: A minimalist single-user note-taking app with SSR, fast in-memory tree operations, and efficient sync.
|
|
5
5
|
Project-URL: Homepage, https://github.com/evolvingstuff/metalist
|
|
6
6
|
Project-URL: Repository, https://github.com/evolvingstuff/metalist
|
|
@@ -49,6 +49,14 @@ A minimalist single-user note-taking app focused on server-side rendering (SSR),
|
|
|
49
49
|
- Multi-tab search contexts with server-persisted scroll/search state (survives browser restarts)
|
|
50
50
|
- Manual namespace backups/restores to a user-selected backup folder with retention controls
|
|
51
51
|
|
|
52
|
+
## Changes in 0.10.0
|
|
53
|
+
|
|
54
|
+
- Excalidraw diagrams: add one from the note's right-click menu or the command palette, and double-click a diagram to edit it full screen. Diagrams autosave, show light and dark previews, resize like images, and each editing session is one undo step. The editor is bundled locally and needs no Node server.
|
|
55
|
+
- The dark theme uses neutral greys with a brighter accent, the light theme header is darker, and Note Layout & Appearance adds note corner and tag style settings.
|
|
56
|
+
- References to a note that starts with an image or diagram show its thumbnail. Collapsed notes, reference titles, and backlink previews show only the first line.
|
|
57
|
+
- Fixed a command palette crash on queries with no matches.
|
|
58
|
+
- The database schema moves to version 10. Password-protected namespaces migrate after unlock, with the usual automatic backup first.
|
|
59
|
+
|
|
52
60
|
## Changes in 0.9.0
|
|
53
61
|
|
|
54
62
|
- AI chat can browse the web in disabled, contextual, or unrestricted mode, fetch multiple pages concurrently, and cite the specific pages used as evidence.
|
|
@@ -12,6 +12,14 @@ A minimalist single-user note-taking app focused on server-side rendering (SSR),
|
|
|
12
12
|
- Multi-tab search contexts with server-persisted scroll/search state (survives browser restarts)
|
|
13
13
|
- Manual namespace backups/restores to a user-selected backup folder with retention controls
|
|
14
14
|
|
|
15
|
+
## Changes in 0.10.0
|
|
16
|
+
|
|
17
|
+
- Excalidraw diagrams: add one from the note's right-click menu or the command palette, and double-click a diagram to edit it full screen. Diagrams autosave, show light and dark previews, resize like images, and each editing session is one undo step. The editor is bundled locally and needs no Node server.
|
|
18
|
+
- The dark theme uses neutral greys with a brighter accent, the light theme header is darker, and Note Layout & Appearance adds note corner and tag style settings.
|
|
19
|
+
- References to a note that starts with an image or diagram show its thumbnail. Collapsed notes, reference titles, and backlink previews show only the first line.
|
|
20
|
+
- Fixed a command palette crash on queries with no matches.
|
|
21
|
+
- The database schema moves to version 10. Password-protected namespaces migrate after unlock, with the usual automatic backup first.
|
|
22
|
+
|
|
15
23
|
## Changes in 0.9.0
|
|
16
24
|
|
|
17
25
|
- AI chat can browse the web in disabled, contextual, or unrestricted mode, fetch multiple pages concurrently, and cite the specific pages used as evidence.
|
|
@@ -206,6 +206,19 @@ class ResizeNoteImageRequest(TypedDict):
|
|
|
206
206
|
undoContext: UndoContext
|
|
207
207
|
viewport: Viewport
|
|
208
208
|
|
|
209
|
+
@with_config(ConfigDict(strict=True))
|
|
210
|
+
class FinishFileEditSessionRequest(TypedDict):
|
|
211
|
+
clientId: Identifier
|
|
212
|
+
hostNoteId: Identifier
|
|
213
|
+
contentRevision: Annotated[int, Field(ge=1)]
|
|
214
|
+
isNewDiagram: bool
|
|
215
|
+
undoContext: UndoContext
|
|
216
|
+
viewport: Viewport
|
|
217
|
+
|
|
218
|
+
@with_config(ConfigDict(strict=True))
|
|
219
|
+
class DiscardFileEditSessionRequest(TypedDict):
|
|
220
|
+
contentRevision: Annotated[int, Field(ge=1)]
|
|
221
|
+
|
|
209
222
|
@with_config(ConfigDict(strict=True))
|
|
210
223
|
class RunShellEndpointRequest(TypedDict):
|
|
211
224
|
timeoutSeconds: Annotated[int, Field(ge=0, le=SHELL_SECONDS)]
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
from urllib.parse import quote
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, File, Form, HTTPException, Request, UploadFile
|
|
8
|
+
from fastapi.responses import Response, StreamingResponse
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
from app.api.note_requests import DiscardFileEditSessionRequest, FinishFileEditSessionRequest
|
|
12
|
+
from app.api.transactions import transactional_route
|
|
13
|
+
from app.api.upload_limits import read_attachment
|
|
14
|
+
from app.api.request_auth import require_request_auth_token
|
|
15
|
+
from app.services.file_registry import file_registry
|
|
16
|
+
from app.services.note_store import store as note_store
|
|
17
|
+
from app.db.file_schema import FILE_PREVIEW_VARIANTS
|
|
18
|
+
from app.services.file_storage import (
|
|
19
|
+
create_file,
|
|
20
|
+
download_file,
|
|
21
|
+
get_file_preview,
|
|
22
|
+
replace_file_content,
|
|
23
|
+
store_file_previews,
|
|
24
|
+
trim_unused_files,
|
|
25
|
+
)
|
|
26
|
+
from app.usecases.file_edit_session import (
|
|
27
|
+
CmdDiscardFileEditSession,
|
|
28
|
+
CmdFinishFileEditSession,
|
|
29
|
+
CmdStartFileEditSession,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
router = APIRouter(prefix="/files", tags=["files"])
|
|
33
|
+
|
|
34
|
+
FILE_REVISION_HEADER = "X-MetaList-File-Revision"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class UploadedFileResponse(BaseModel):
|
|
38
|
+
file_id: str
|
|
39
|
+
reference_token: str
|
|
40
|
+
title: str
|
|
41
|
+
original_filename: str
|
|
42
|
+
mime_type: str
|
|
43
|
+
size_bytes: int
|
|
44
|
+
thumbnail_kind: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class FileContentUpdatedResponse(BaseModel):
|
|
48
|
+
file_id: str
|
|
49
|
+
content_revision: int
|
|
50
|
+
size_bytes: int
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class FilePreviewsStoredResponse(BaseModel):
|
|
54
|
+
file_id: str
|
|
55
|
+
content_revision: int
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TrimUnusedFilesResponse(BaseModel):
|
|
59
|
+
deleted_count: int
|
|
60
|
+
deleted_file_ids: list[str]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _require_bearer_token(request: Request) -> str:
|
|
64
|
+
return require_request_auth_token(request)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _require_known_file(file_id: str) -> None:
|
|
68
|
+
if not file_registry.has_file(file_id):
|
|
69
|
+
raise HTTPException(status_code=404, detail=f"File not found: {file_id}")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@router.post("/upload", response_model=UploadedFileResponse)
|
|
73
|
+
@transactional_route
|
|
74
|
+
async def upload_file_endpoint(
|
|
75
|
+
request: Request,
|
|
76
|
+
file: Annotated[UploadFile, File()],
|
|
77
|
+
):
|
|
78
|
+
token = _require_bearer_token(request)
|
|
79
|
+
if not isinstance(file.filename, str) or file.filename == "":
|
|
80
|
+
raise HTTPException(status_code=400, detail="Uploaded file must include a filename")
|
|
81
|
+
if not isinstance(file.content_type, str) or file.content_type == "":
|
|
82
|
+
raise HTTPException(status_code=400, detail="Uploaded file must include a non-empty MIME type")
|
|
83
|
+
|
|
84
|
+
content_bytes = await read_attachment(file)
|
|
85
|
+
|
|
86
|
+
record = create_file(
|
|
87
|
+
original_filename=file.filename,
|
|
88
|
+
mime_type=file.content_type,
|
|
89
|
+
content_bytes=content_bytes,
|
|
90
|
+
token=token,
|
|
91
|
+
)
|
|
92
|
+
return UploadedFileResponse(
|
|
93
|
+
file_id=record.id,
|
|
94
|
+
reference_token=f"![[{record.id}]]",
|
|
95
|
+
title=record.title,
|
|
96
|
+
original_filename=record.original_filename,
|
|
97
|
+
mime_type=record.mime_type,
|
|
98
|
+
size_bytes=record.size_bytes,
|
|
99
|
+
thumbnail_kind=record.thumbnail_kind,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@router.get("/{file_id}/download")
|
|
104
|
+
def download_file_endpoint(request: Request, file_id: str):
|
|
105
|
+
token = _require_bearer_token(request)
|
|
106
|
+
if not file_registry.has_file(file_id):
|
|
107
|
+
raise HTTPException(status_code=404, detail=f"File not found: {file_id}")
|
|
108
|
+
|
|
109
|
+
downloaded = download_file(file_id, token)
|
|
110
|
+
quoted_filename = quote(downloaded.record.original_filename, safe="")
|
|
111
|
+
return StreamingResponse(
|
|
112
|
+
BytesIO(downloaded.content_bytes),
|
|
113
|
+
media_type=downloaded.record.mime_type,
|
|
114
|
+
headers={
|
|
115
|
+
"Content-Disposition": f"attachment; filename*=UTF-8''{quoted_filename}",
|
|
116
|
+
FILE_REVISION_HEADER: str(downloaded.record.content_revision),
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@router.put("/{file_id}/content", response_model=FileContentUpdatedResponse)
|
|
122
|
+
@transactional_route
|
|
123
|
+
async def replace_file_content_endpoint(
|
|
124
|
+
request: Request,
|
|
125
|
+
file_id: str,
|
|
126
|
+
file: Annotated[UploadFile, File()],
|
|
127
|
+
expected_revision: Annotated[int, Form()],
|
|
128
|
+
):
|
|
129
|
+
token = _require_bearer_token(request)
|
|
130
|
+
_require_known_file(file_id)
|
|
131
|
+
content_bytes = await read_attachment(file)
|
|
132
|
+
record = replace_file_content(
|
|
133
|
+
file_id=file_id,
|
|
134
|
+
content_bytes=content_bytes,
|
|
135
|
+
expected_revision=expected_revision,
|
|
136
|
+
token=token,
|
|
137
|
+
)
|
|
138
|
+
return FileContentUpdatedResponse(
|
|
139
|
+
file_id=record.id,
|
|
140
|
+
content_revision=record.content_revision,
|
|
141
|
+
size_bytes=record.size_bytes,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@router.put("/{file_id}/previews", response_model=FilePreviewsStoredResponse)
|
|
146
|
+
@transactional_route
|
|
147
|
+
async def store_file_previews_endpoint(
|
|
148
|
+
request: Request,
|
|
149
|
+
file_id: str,
|
|
150
|
+
light: Annotated[UploadFile, File()],
|
|
151
|
+
dark: Annotated[UploadFile, File()],
|
|
152
|
+
content_revision: Annotated[int, Form()],
|
|
153
|
+
):
|
|
154
|
+
token = _require_bearer_token(request)
|
|
155
|
+
_require_known_file(file_id)
|
|
156
|
+
previews_by_variant = {
|
|
157
|
+
"light": await read_attachment(light),
|
|
158
|
+
"dark": await read_attachment(dark),
|
|
159
|
+
}
|
|
160
|
+
record = store_file_previews(
|
|
161
|
+
file_id=file_id,
|
|
162
|
+
previews_by_variant=previews_by_variant,
|
|
163
|
+
content_revision=content_revision,
|
|
164
|
+
token=token,
|
|
165
|
+
)
|
|
166
|
+
return FilePreviewsStoredResponse(file_id=record.id, content_revision=record.content_revision)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@router.get("/{file_id}/previews/{variant}")
|
|
170
|
+
def download_file_preview_endpoint(request: Request, file_id: str, variant: str):
|
|
171
|
+
token = _require_bearer_token(request)
|
|
172
|
+
_require_known_file(file_id)
|
|
173
|
+
if variant not in FILE_PREVIEW_VARIANTS:
|
|
174
|
+
raise HTTPException(status_code=404, detail=f"Unknown preview variant: {variant}")
|
|
175
|
+
preview = get_file_preview(file_id=file_id, variant=variant, token=token)
|
|
176
|
+
return Response(
|
|
177
|
+
content=preview.content_bytes,
|
|
178
|
+
media_type=preview.mime_type,
|
|
179
|
+
headers={FILE_REVISION_HEADER: str(preview.content_revision)},
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# A diagram editing session: opened with the editor, finished by Done (one undo step), or discarded.
|
|
184
|
+
@router.post("/{file_id}/edit-sessions")
|
|
185
|
+
@transactional_route
|
|
186
|
+
def start_file_edit_session_endpoint(request: Request, file_id: str) -> dict[str, object]:
|
|
187
|
+
token = _require_bearer_token(request)
|
|
188
|
+
_require_known_file(file_id)
|
|
189
|
+
return CmdStartFileEditSession(file_id=file_id, token=token).execute()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@router.post("/{file_id}/edit-sessions/{session_id}/finish")
|
|
193
|
+
@transactional_route
|
|
194
|
+
def finish_file_edit_session_endpoint(
|
|
195
|
+
request: Request,
|
|
196
|
+
file_id: str,
|
|
197
|
+
session_id: str,
|
|
198
|
+
body: FinishFileEditSessionRequest,
|
|
199
|
+
) -> dict[str, object]:
|
|
200
|
+
token = _require_bearer_token(request)
|
|
201
|
+
_require_known_file(file_id)
|
|
202
|
+
host_note_id = body["hostNoteId"]
|
|
203
|
+
if not note_store.has_note(host_note_id):
|
|
204
|
+
raise HTTPException(status_code=404, detail=f"Note not found: {host_note_id}")
|
|
205
|
+
return CmdFinishFileEditSession(
|
|
206
|
+
file_id=file_id,
|
|
207
|
+
session_id=session_id,
|
|
208
|
+
host_note_id=host_note_id,
|
|
209
|
+
content_revision=body["contentRevision"],
|
|
210
|
+
is_new_diagram=body["isNewDiagram"],
|
|
211
|
+
token=token,
|
|
212
|
+
client_id=body["clientId"],
|
|
213
|
+
undo_context=body["undoContext"],
|
|
214
|
+
viewport=body["viewport"],
|
|
215
|
+
).execute()
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@router.post("/{file_id}/edit-sessions/{session_id}/discard")
|
|
219
|
+
@transactional_route
|
|
220
|
+
def discard_file_edit_session_endpoint(
|
|
221
|
+
request: Request,
|
|
222
|
+
file_id: str,
|
|
223
|
+
session_id: str,
|
|
224
|
+
body: DiscardFileEditSessionRequest,
|
|
225
|
+
) -> dict[str, object]:
|
|
226
|
+
token = _require_bearer_token(request)
|
|
227
|
+
_require_known_file(file_id)
|
|
228
|
+
return CmdDiscardFileEditSession(
|
|
229
|
+
file_id=file_id,
|
|
230
|
+
session_id=session_id,
|
|
231
|
+
content_revision=body["contentRevision"],
|
|
232
|
+
token=token,
|
|
233
|
+
).execute()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@router.post("/trim-unused", response_model=TrimUnusedFilesResponse)
|
|
237
|
+
@transactional_route
|
|
238
|
+
def trim_unused_files_endpoint(request: Request):
|
|
239
|
+
_require_bearer_token(request)
|
|
240
|
+
result = trim_unused_files()
|
|
241
|
+
return TrimUnusedFilesResponse(
|
|
242
|
+
deleted_count=result.deleted_count,
|
|
243
|
+
deleted_file_ids=result.deleted_file_ids,
|
|
244
|
+
)
|
|
@@ -23,6 +23,18 @@ async def read_attachment(file: UploadFile) -> bytes:
|
|
|
23
23
|
await file.close()
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def _is_upload_request(scope) -> bool:
|
|
27
|
+
path = scope['path']
|
|
28
|
+
if path.endswith('/files/upload'):
|
|
29
|
+
return True
|
|
30
|
+
# In-place updates of MetaList-edited files (Excalidraw scenes and their previews) are uploads too.
|
|
31
|
+
if '/files/' not in path:
|
|
32
|
+
return False
|
|
33
|
+
if not path.endswith('/content') and not path.endswith('/previews'):
|
|
34
|
+
return False
|
|
35
|
+
return scope['method'] == 'PUT'
|
|
36
|
+
|
|
37
|
+
|
|
26
38
|
class UploadLimitMiddleware:
|
|
27
39
|
def __init__(self, app):
|
|
28
40
|
self.app = app
|
|
@@ -32,7 +44,7 @@ class UploadLimitMiddleware:
|
|
|
32
44
|
if scope['type'] != 'http':
|
|
33
45
|
await self.app(scope, receive, send)
|
|
34
46
|
return
|
|
35
|
-
is_upload = scope
|
|
47
|
+
is_upload = _is_upload_request(scope)
|
|
36
48
|
is_download = '/files/' in scope['path'] and scope['path'].endswith('/download')
|
|
37
49
|
if not (is_upload or is_download):
|
|
38
50
|
await self.app(scope, receive, send)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Migration 9→10: let MetaList edit some attachments in place (Excalidraw diagrams).
|
|
2
|
+
|
|
3
|
+
The sibling files database gains `files.content_revision` and the encrypted `file_previews`
|
|
4
|
+
table. Opening a files database also adds them (see `initialize_file_schema`), so this step is
|
|
5
|
+
idempotent; it exists so the upgrade is recorded in the namespace version like every other
|
|
6
|
+
schema change, and so the encryption audit can require the new schema from version 10 on.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from contextlib import closing
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import sqlite3
|
|
12
|
+
|
|
13
|
+
from app.db.file_schema import FILE_PREVIEWS_TABLE, FILES_TABLE, initialize_file_schema
|
|
14
|
+
|
|
15
|
+
# Same shared in-memory database FileSession uses when the notes database is in memory.
|
|
16
|
+
_FILE_MEMORY_URI = "file:metalist_files_memory?mode=memory&cache=shared"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _upgrade(files: sqlite3.Connection) -> None:
|
|
20
|
+
initialize_file_schema(files)
|
|
21
|
+
files.commit()
|
|
22
|
+
columns = {row[1] for row in files.execute(f"PRAGMA table_info({FILES_TABLE})").fetchall()}
|
|
23
|
+
if "content_revision" not in columns:
|
|
24
|
+
raise RuntimeError("Database migration 9→10 did not add files.content_revision")
|
|
25
|
+
preview_table = files.execute(
|
|
26
|
+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?", (FILE_PREVIEWS_TABLE,)
|
|
27
|
+
).fetchone()
|
|
28
|
+
if preview_table is None:
|
|
29
|
+
raise RuntimeError(f"Database migration 9→10 did not create {FILE_PREVIEWS_TABLE}")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def add_editable_file_schema(
|
|
33
|
+
*,
|
|
34
|
+
connection: sqlite3.Connection,
|
|
35
|
+
encryption_enabled: bool, # noqa: ARG001
|
|
36
|
+
encryption_service: object, # noqa: ARG001
|
|
37
|
+
) -> int:
|
|
38
|
+
database = connection.execute("PRAGMA database_list").fetchone()[2]
|
|
39
|
+
if not database:
|
|
40
|
+
with closing(sqlite3.connect(_FILE_MEMORY_URI, uri=True)) as files:
|
|
41
|
+
_upgrade(files)
|
|
42
|
+
return 0
|
|
43
|
+
notes = Path(database)
|
|
44
|
+
target = notes.with_name(f"{notes.stem}.files{notes.suffix}")
|
|
45
|
+
# A namespace without attachments gets the current schema when its files database is created.
|
|
46
|
+
if not target.exists():
|
|
47
|
+
return 0
|
|
48
|
+
with closing(sqlite3.connect(target)) as files:
|
|
49
|
+
_upgrade(files)
|
|
50
|
+
return 0
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""SQLite schema helpers for encrypted file storage."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from sqlite3 import Connection
|
|
6
|
+
|
|
7
|
+
FILES_TABLE = "files"
|
|
8
|
+
FILE_PREVIEWS_TABLE = "file_previews"
|
|
9
|
+
FILE_PREVIEW_VARIANTS = ("light", "dark")
|
|
10
|
+
|
|
11
|
+
_CREATE_FILES_TABLE = f"""
|
|
12
|
+
CREATE TABLE IF NOT EXISTS {FILES_TABLE} (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
title TEXT NOT NULL,
|
|
15
|
+
title_encryption_nonce BLOB,
|
|
16
|
+
title_encryption_tag BLOB,
|
|
17
|
+
metadata_json TEXT NOT NULL,
|
|
18
|
+
metadata_encryption_nonce BLOB,
|
|
19
|
+
metadata_encryption_tag BLOB,
|
|
20
|
+
blob_data BLOB NOT NULL,
|
|
21
|
+
blob_encryption_nonce BLOB,
|
|
22
|
+
blob_encryption_tag BLOB,
|
|
23
|
+
created_at TEXT NOT NULL,
|
|
24
|
+
updated_at TEXT NOT NULL,
|
|
25
|
+
content_revision INTEGER NOT NULL DEFAULT 1
|
|
26
|
+
);
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# Rendered previews for files edited inside MetaList (Excalidraw diagrams).
|
|
30
|
+
# One row per theme variant; content_revision records which file revision was rendered.
|
|
31
|
+
_CREATE_FILE_PREVIEWS_TABLE = f"""
|
|
32
|
+
CREATE TABLE IF NOT EXISTS {FILE_PREVIEWS_TABLE} (
|
|
33
|
+
file_id TEXT NOT NULL REFERENCES {FILES_TABLE}(id) ON DELETE CASCADE,
|
|
34
|
+
variant TEXT NOT NULL CHECK (variant IN ('light', 'dark')),
|
|
35
|
+
mime_type TEXT NOT NULL,
|
|
36
|
+
preview_data BLOB NOT NULL,
|
|
37
|
+
preview_encryption_nonce BLOB,
|
|
38
|
+
preview_encryption_tag BLOB,
|
|
39
|
+
content_revision INTEGER NOT NULL,
|
|
40
|
+
updated_at TEXT NOT NULL,
|
|
41
|
+
PRIMARY KEY (file_id, variant)
|
|
42
|
+
);
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _add_content_revision_column_if_missing(connection: Connection) -> None:
|
|
47
|
+
"""Upgrade live file databases created before in-place file updates existed."""
|
|
48
|
+
column_names = {row[1] for row in connection.execute(f"PRAGMA table_info({FILES_TABLE})").fetchall()}
|
|
49
|
+
assert "id" in column_names, "files table must exist before its columns are upgraded"
|
|
50
|
+
if "content_revision" in column_names:
|
|
51
|
+
return
|
|
52
|
+
connection.execute(f"ALTER TABLE {FILES_TABLE} ADD COLUMN content_revision INTEGER NOT NULL DEFAULT 1")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def initialize_file_schema(connection: Connection) -> None:
|
|
56
|
+
connection.execute(_CREATE_FILES_TABLE)
|
|
57
|
+
_add_content_revision_column_if_missing(connection)
|
|
58
|
+
connection.execute(_CREATE_FILE_PREVIEWS_TABLE)
|
|
@@ -6,7 +6,7 @@ import sqlite3
|
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from typing import Iterable, Optional
|
|
8
8
|
|
|
9
|
-
from app.db.file_schema import FILES_TABLE
|
|
9
|
+
from app.db.file_schema import FILE_PREVIEW_VARIANTS, FILE_PREVIEWS_TABLE, FILES_TABLE
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def _serialize_datetime(value: datetime) -> str:
|
|
@@ -24,6 +24,10 @@ def _deserialize_row(row: sqlite3.Row) -> dict[str, object]:
|
|
|
24
24
|
raise TypeError(f"files.{field} must be a string | file_id={file_id} value={raw!r}")
|
|
25
25
|
return datetime.fromisoformat(raw)
|
|
26
26
|
|
|
27
|
+
content_revision = row["content_revision"]
|
|
28
|
+
if not isinstance(content_revision, int) or content_revision < 1:
|
|
29
|
+
raise TypeError(f"files.content_revision must be a positive integer | file_id={file_id} value={content_revision!r}")
|
|
30
|
+
|
|
27
31
|
return {
|
|
28
32
|
"id": file_id,
|
|
29
33
|
"title": row["title"],
|
|
@@ -37,6 +41,7 @@ def _deserialize_row(row: sqlite3.Row) -> dict[str, object]:
|
|
|
37
41
|
"blob_encryption_tag": row["blob_encryption_tag"],
|
|
38
42
|
"created_at": _parse_datetime("created_at"),
|
|
39
43
|
"updated_at": _parse_datetime("updated_at"),
|
|
44
|
+
"content_revision": content_revision,
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
|
|
@@ -90,7 +95,7 @@ def insert_file(
|
|
|
90
95
|
)
|
|
91
96
|
|
|
92
97
|
|
|
93
|
-
_METADATA_COLUMNS = "id, title, title_encryption_nonce, title_encryption_tag, metadata_json, metadata_encryption_nonce, metadata_encryption_tag, created_at, updated_at, X'' AS blob_data, blob_encryption_nonce, blob_encryption_tag"
|
|
98
|
+
_METADATA_COLUMNS = "id, title, title_encryption_nonce, title_encryption_tag, metadata_json, metadata_encryption_nonce, metadata_encryption_tag, created_at, updated_at, content_revision, X'' AS blob_data, blob_encryption_nonce, blob_encryption_tag"
|
|
94
99
|
|
|
95
100
|
|
|
96
101
|
class AttachmentSizeExceeded(ValueError):
|
|
@@ -200,3 +205,157 @@ def delete_files(connection: sqlite3.Connection, file_ids: Iterable[str]) -> int
|
|
|
200
205
|
tuple(identifiers),
|
|
201
206
|
)
|
|
202
207
|
return int(cursor.rowcount)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def replace_file_content_fields(
|
|
211
|
+
connection: sqlite3.Connection,
|
|
212
|
+
*,
|
|
213
|
+
file_id: str,
|
|
214
|
+
metadata_json: str,
|
|
215
|
+
metadata_encryption_nonce: Optional[bytes],
|
|
216
|
+
metadata_encryption_tag: Optional[bytes],
|
|
217
|
+
blob_data: bytes,
|
|
218
|
+
blob_encryption_nonce: Optional[bytes],
|
|
219
|
+
blob_encryption_tag: Optional[bytes],
|
|
220
|
+
expected_revision: int,
|
|
221
|
+
updated_at: datetime,
|
|
222
|
+
) -> int:
|
|
223
|
+
"""Replace a file's content when it is still at expected_revision; returns the updated row count."""
|
|
224
|
+
if not isinstance(expected_revision, int) or expected_revision < 1:
|
|
225
|
+
raise TypeError(f"expected_revision must be a positive integer, got {expected_revision!r}")
|
|
226
|
+
cursor = connection.execute(
|
|
227
|
+
f"""
|
|
228
|
+
UPDATE {FILES_TABLE}
|
|
229
|
+
SET
|
|
230
|
+
metadata_json = ?,
|
|
231
|
+
metadata_encryption_nonce = ?,
|
|
232
|
+
metadata_encryption_tag = ?,
|
|
233
|
+
blob_data = ?,
|
|
234
|
+
blob_encryption_nonce = ?,
|
|
235
|
+
blob_encryption_tag = ?,
|
|
236
|
+
updated_at = ?,
|
|
237
|
+
content_revision = content_revision + 1
|
|
238
|
+
WHERE id = ? AND content_revision = ?
|
|
239
|
+
""",
|
|
240
|
+
(
|
|
241
|
+
metadata_json,
|
|
242
|
+
metadata_encryption_nonce,
|
|
243
|
+
metadata_encryption_tag,
|
|
244
|
+
blob_data,
|
|
245
|
+
blob_encryption_nonce,
|
|
246
|
+
blob_encryption_tag,
|
|
247
|
+
_serialize_datetime(updated_at),
|
|
248
|
+
file_id,
|
|
249
|
+
expected_revision,
|
|
250
|
+
),
|
|
251
|
+
)
|
|
252
|
+
return int(cursor.rowcount)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _require_preview_variant(variant: str) -> None:
|
|
256
|
+
if variant not in FILE_PREVIEW_VARIANTS:
|
|
257
|
+
raise ValueError(f"file preview variant must be one of {FILE_PREVIEW_VARIANTS}, got {variant!r}")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _deserialize_preview_row(row: sqlite3.Row) -> dict[str, object]:
|
|
261
|
+
file_id = row["file_id"]
|
|
262
|
+
content_revision = row["content_revision"]
|
|
263
|
+
if not isinstance(content_revision, int) or content_revision < 1:
|
|
264
|
+
raise TypeError(f"file_previews.content_revision invalid | file_id={file_id} value={content_revision!r}")
|
|
265
|
+
updated_at = row["updated_at"]
|
|
266
|
+
if not isinstance(updated_at, str):
|
|
267
|
+
raise TypeError(f"file_previews.updated_at must be a string | file_id={file_id}")
|
|
268
|
+
return {
|
|
269
|
+
"file_id": file_id,
|
|
270
|
+
"variant": row["variant"],
|
|
271
|
+
"mime_type": row["mime_type"],
|
|
272
|
+
"preview_data": row["preview_data"],
|
|
273
|
+
"preview_encryption_nonce": row["preview_encryption_nonce"],
|
|
274
|
+
"preview_encryption_tag": row["preview_encryption_tag"],
|
|
275
|
+
"content_revision": content_revision,
|
|
276
|
+
"updated_at": datetime.fromisoformat(updated_at),
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def upsert_file_preview(
|
|
281
|
+
connection: sqlite3.Connection,
|
|
282
|
+
*,
|
|
283
|
+
file_id: str,
|
|
284
|
+
variant: str,
|
|
285
|
+
mime_type: str,
|
|
286
|
+
preview_data: bytes,
|
|
287
|
+
preview_encryption_nonce: Optional[bytes],
|
|
288
|
+
preview_encryption_tag: Optional[bytes],
|
|
289
|
+
content_revision: int,
|
|
290
|
+
updated_at: datetime,
|
|
291
|
+
) -> None:
|
|
292
|
+
_require_preview_variant(variant)
|
|
293
|
+
connection.execute(
|
|
294
|
+
f"""
|
|
295
|
+
INSERT INTO {FILE_PREVIEWS_TABLE} (
|
|
296
|
+
file_id, variant, mime_type, preview_data, preview_encryption_nonce,
|
|
297
|
+
preview_encryption_tag, content_revision, updated_at
|
|
298
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
299
|
+
ON CONFLICT(file_id, variant) DO UPDATE SET
|
|
300
|
+
mime_type = excluded.mime_type,
|
|
301
|
+
preview_data = excluded.preview_data,
|
|
302
|
+
preview_encryption_nonce = excluded.preview_encryption_nonce,
|
|
303
|
+
preview_encryption_tag = excluded.preview_encryption_tag,
|
|
304
|
+
content_revision = excluded.content_revision,
|
|
305
|
+
updated_at = excluded.updated_at
|
|
306
|
+
""",
|
|
307
|
+
(
|
|
308
|
+
file_id,
|
|
309
|
+
variant,
|
|
310
|
+
mime_type,
|
|
311
|
+
preview_data,
|
|
312
|
+
preview_encryption_nonce,
|
|
313
|
+
preview_encryption_tag,
|
|
314
|
+
content_revision,
|
|
315
|
+
_serialize_datetime(updated_at),
|
|
316
|
+
),
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def delete_file_previews(connection: sqlite3.Connection, file_id: str) -> int:
|
|
321
|
+
cursor = connection.execute(f"DELETE FROM {FILE_PREVIEWS_TABLE} WHERE file_id = ?", (file_id,))
|
|
322
|
+
return cursor.rowcount
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def fetch_file_preview(connection: sqlite3.Connection, file_id: str, variant: str) -> Optional[dict[str, object]]:
|
|
326
|
+
_require_preview_variant(variant)
|
|
327
|
+
row = connection.execute(
|
|
328
|
+
f"SELECT * FROM {FILE_PREVIEWS_TABLE} WHERE file_id = ? AND variant = ?",
|
|
329
|
+
(file_id, variant),
|
|
330
|
+
).fetchone()
|
|
331
|
+
if row is None:
|
|
332
|
+
return None
|
|
333
|
+
return _deserialize_preview_row(row)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def fetch_all_file_previews(connection: sqlite3.Connection) -> list[dict[str, object]]:
|
|
337
|
+
rows = connection.execute(f"SELECT * FROM {FILE_PREVIEWS_TABLE} ORDER BY file_id ASC, variant ASC").fetchall()
|
|
338
|
+
return [_deserialize_preview_row(row) for row in rows]
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def update_file_preview_storage_fields(
|
|
342
|
+
connection: sqlite3.Connection,
|
|
343
|
+
*,
|
|
344
|
+
file_id: str,
|
|
345
|
+
variant: str,
|
|
346
|
+
preview_data: bytes,
|
|
347
|
+
preview_encryption_nonce: Optional[bytes],
|
|
348
|
+
preview_encryption_tag: Optional[bytes],
|
|
349
|
+
) -> None:
|
|
350
|
+
"""Rewrite preview ciphertext during encryption migrations without changing its revision."""
|
|
351
|
+
_require_preview_variant(variant)
|
|
352
|
+
cursor = connection.execute(
|
|
353
|
+
f"""
|
|
354
|
+
UPDATE {FILE_PREVIEWS_TABLE}
|
|
355
|
+
SET preview_data = ?, preview_encryption_nonce = ?, preview_encryption_tag = ?
|
|
356
|
+
WHERE file_id = ? AND variant = ?
|
|
357
|
+
""",
|
|
358
|
+
(preview_data, preview_encryption_nonce, preview_encryption_tag, file_id, variant),
|
|
359
|
+
)
|
|
360
|
+
if cursor.rowcount != 1:
|
|
361
|
+
raise RuntimeError(f"file preview rewrite expected one row | file_id={file_id} variant={variant}")
|
|
@@ -8,6 +8,7 @@ import sqlite3
|
|
|
8
8
|
import re
|
|
9
9
|
|
|
10
10
|
from app.db.version import CURRENT_DATABASE_VERSION
|
|
11
|
+
from app.db.editable_files_migration import add_editable_file_schema
|
|
11
12
|
from app.db.retire_sounds import retire_sounds
|
|
12
13
|
from app.db.schema import create_namespace_content_migrations_table
|
|
13
14
|
from app.services.encryption import EncryptionService
|
|
@@ -382,6 +383,7 @@ _MIGRATIONS: dict[
|
|
|
382
383
|
6: _migration_6_to_7,
|
|
383
384
|
7: _migration_7_to_8,
|
|
384
385
|
8: retire_sounds,
|
|
386
|
+
9: add_editable_file_schema,
|
|
385
387
|
}
|
|
386
388
|
|
|
387
389
|
|