cli-jaw 2.0.1 → 2.0.2
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.
- package/README.ko.md +3 -2
- package/README.md +41 -12
- package/dist/bin/_http-client.js +34 -0
- package/dist/bin/_http-client.js.map +1 -0
- package/dist/bin/cli-jaw.js +10 -5
- package/dist/bin/cli-jaw.js.map +1 -1
- package/dist/bin/commands/browser-web-ai.js +155 -38
- package/dist/bin/commands/browser-web-ai.js.map +1 -1
- package/dist/bin/commands/browser.js +265 -37
- package/dist/bin/commands/browser.js.map +1 -1
- package/dist/bin/commands/chat.js +18 -14
- package/dist/bin/commands/chat.js.map +1 -1
- package/dist/bin/commands/clone.js +3 -2
- package/dist/bin/commands/clone.js.map +1 -1
- package/dist/bin/commands/dashboard.js +22 -18
- package/dist/bin/commands/dashboard.js.map +1 -1
- package/dist/bin/commands/dispatch.js +9 -11
- package/dist/bin/commands/dispatch.js.map +1 -1
- package/dist/bin/commands/doctor.js +26 -14
- package/dist/bin/commands/doctor.js.map +1 -1
- package/dist/bin/commands/employee.js +12 -7
- package/dist/bin/commands/employee.js.map +1 -1
- package/dist/bin/commands/init.js +9 -9
- package/dist/bin/commands/init.js.map +1 -1
- package/dist/bin/commands/launchd.js +17 -7
- package/dist/bin/commands/launchd.js.map +1 -1
- package/dist/bin/commands/mcp.js +7 -4
- package/dist/bin/commands/mcp.js.map +1 -1
- package/dist/bin/commands/memory.js +7 -5
- package/dist/bin/commands/memory.js.map +1 -1
- package/dist/bin/commands/orchestrate.js +6 -3
- package/dist/bin/commands/orchestrate.js.map +1 -1
- package/dist/bin/commands/reset.js +4 -3
- package/dist/bin/commands/reset.js.map +1 -1
- package/dist/bin/commands/serve.js +3 -2
- package/dist/bin/commands/serve.js.map +1 -1
- package/dist/bin/commands/service.js +2 -2
- package/dist/bin/commands/service.js.map +1 -1
- package/dist/bin/commands/status.js +10 -9
- package/dist/bin/commands/status.js.map +1 -1
- package/dist/bin/commands/tui/api.js +26 -18
- package/dist/bin/commands/tui/api.js.map +1 -1
- package/dist/bin/commands/tui/overlays.js +3 -1
- package/dist/bin/commands/tui/overlays.js.map +1 -1
- package/dist/bin/commands/tui/simple-mode.js +1 -1
- package/dist/bin/commands/tui/simple-mode.js.map +1 -1
- package/dist/bin/commands/tui/types.js.map +1 -1
- package/dist/bin/postinstall.js +153 -65
- package/dist/bin/postinstall.js.map +1 -1
- package/dist/bin/star-prompt.js +4 -3
- package/dist/bin/star-prompt.js.map +1 -1
- package/dist/lib/mcp/format-converters.js +15 -10
- package/dist/lib/mcp/format-converters.js.map +1 -1
- package/dist/lib/mcp/mcp-install.js +2 -2
- package/dist/lib/mcp/mcp-install.js.map +1 -1
- package/dist/lib/mcp/skills-distribution.js +2 -2
- package/dist/lib/mcp/skills-distribution.js.map +1 -1
- package/dist/lib/mcp/skills-reset.js +5 -4
- package/dist/lib/mcp/skills-reset.js.map +1 -1
- package/dist/lib/mcp/skills-symlinks.js +9 -9
- package/dist/lib/mcp/skills-symlinks.js.map +1 -1
- package/dist/lib/mcp/skills-utils.js +5 -4
- package/dist/lib/mcp/skills-utils.js.map +1 -1
- package/dist/lib/mcp/unified-config.js +3 -2
- package/dist/lib/mcp/unified-config.js.map +1 -1
- package/dist/lib/mime-detect.js +65 -0
- package/dist/lib/mime-detect.js.map +1 -0
- package/dist/lib/quota-copilot.js +12 -10
- package/dist/lib/quota-copilot.js.map +1 -1
- package/dist/lib/stt.js +3 -3
- package/dist/lib/stt.js.map +1 -1
- package/dist/lib/upload.js +14 -5
- package/dist/lib/upload.js.map +1 -1
- package/dist/scripts/i18n-registry.js +4 -4
- package/dist/scripts/i18n-registry.js.map +1 -1
- package/dist/server.js +61 -57
- package/dist/server.js.map +1 -1
- package/dist/src/agent/alert-escalation.js +61 -0
- package/dist/src/agent/alert-escalation.js.map +1 -0
- package/dist/src/agent/args.js +71 -2
- package/dist/src/agent/args.js.map +1 -1
- package/dist/src/agent/error-classifier.js +9 -4
- package/dist/src/agent/error-classifier.js.map +1 -1
- package/dist/src/agent/events.js +130 -101
- package/dist/src/agent/events.js.map +1 -1
- package/dist/src/agent/lifecycle-handler.js +44 -19
- package/dist/src/agent/lifecycle-handler.js.map +1 -1
- package/dist/src/agent/live-run-state.js +7 -3
- package/dist/src/agent/live-run-state.js.map +1 -1
- package/dist/src/agent/memory-flush-controller.js +6 -4
- package/dist/src/agent/memory-flush-controller.js.map +1 -1
- package/dist/src/agent/opencode-diagnostics.js +7 -6
- package/dist/src/agent/opencode-diagnostics.js.map +1 -1
- package/dist/src/agent/session-persistence.js +1 -1
- package/dist/src/agent/session-persistence.js.map +1 -1
- package/dist/src/agent/spawn-env.js +33 -16
- package/dist/src/agent/spawn-env.js.map +1 -1
- package/dist/src/agent/spawn.js +176 -110
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/agent/watchdog.js +56 -0
- package/dist/src/agent/watchdog.js.map +1 -0
- package/dist/src/browser/actions.js +179 -70
- package/dist/src/browser/actions.js.map +1 -1
- package/dist/src/browser/connection.js +296 -19
- package/dist/src/browser/connection.js.map +1 -1
- package/dist/src/browser/index.js +4 -1
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/browser/launch-policy.js +1 -1
- package/dist/src/browser/launch-policy.js.map +1 -1
- package/dist/src/browser/primitives.js +11 -7
- package/dist/src/browser/primitives.js.map +1 -1
- package/dist/src/browser/runtime-diagnostics.js +45 -0
- package/dist/src/browser/runtime-diagnostics.js.map +1 -0
- package/dist/src/browser/runtime-orphans.js +127 -0
- package/dist/src/browser/runtime-orphans.js.map +1 -0
- package/dist/src/browser/runtime-owner-store.js +51 -0
- package/dist/src/browser/runtime-owner-store.js.map +1 -0
- package/dist/src/browser/runtime-owner.js +2 -2
- package/dist/src/browser/runtime-owner.js.map +1 -1
- package/dist/src/browser/tab-lifecycle.js +157 -0
- package/dist/src/browser/tab-lifecycle.js.map +1 -0
- package/dist/src/browser/vision.js +35 -11
- package/dist/src/browser/vision.js.map +1 -1
- package/dist/src/browser/web-ai/action-breadth.js +56 -0
- package/dist/src/browser/web-ai/action-breadth.js.map +1 -0
- package/dist/src/browser/web-ai/action-cache.js +9 -8
- package/dist/src/browser/web-ai/action-cache.js.map +1 -1
- package/dist/src/browser/web-ai/action-intent.js +64 -0
- package/dist/src/browser/web-ai/action-intent.js.map +1 -0
- package/dist/src/browser/web-ai/action-memory.js +82 -0
- package/dist/src/browser/web-ai/action-memory.js.map +1 -0
- package/dist/src/browser/web-ai/action-trace.js +11 -6
- package/dist/src/browser/web-ai/action-trace.js.map +1 -1
- package/dist/src/browser/web-ai/annotated-screenshot.js +2 -1
- package/dist/src/browser/web-ai/annotated-screenshot.js.map +1 -1
- package/dist/src/browser/web-ai/answer-artifact.js +97 -0
- package/dist/src/browser/web-ai/answer-artifact.js.map +1 -0
- package/dist/src/browser/web-ai/ax-snapshot.js +3 -2
- package/dist/src/browser/web-ai/ax-snapshot.js.map +1 -1
- package/dist/src/browser/web-ai/browser-primitives.js +10 -7
- package/dist/src/browser/web-ai/browser-primitives.js.map +1 -1
- package/dist/src/browser/web-ai/capability-registry.js +1 -1
- package/dist/src/browser/web-ai/capability-registry.js.map +1 -1
- package/dist/src/browser/web-ai/chatgpt-composer.js +12 -12
- package/dist/src/browser/web-ai/chatgpt-composer.js.map +1 -1
- package/dist/src/browser/web-ai/chatgpt-model.js +469 -50
- package/dist/src/browser/web-ai/chatgpt-model.js.map +1 -1
- package/dist/src/browser/web-ai/chatgpt-response.js +82 -10
- package/dist/src/browser/web-ai/chatgpt-response.js.map +1 -1
- package/dist/src/browser/web-ai/chatgpt.js +325 -49
- package/dist/src/browser/web-ai/chatgpt.js.map +1 -1
- package/dist/src/browser/web-ai/churn-log.js +1 -1
- package/dist/src/browser/web-ai/churn-log.js.map +1 -1
- package/dist/src/browser/web-ai/cli-sessions.js +42 -26
- package/dist/src/browser/web-ai/cli-sessions.js.map +1 -1
- package/dist/src/browser/web-ai/context-pack/builder.js +1 -1
- package/dist/src/browser/web-ai/context-pack/builder.js.map +1 -1
- package/dist/src/browser/web-ai/context-pack/file-selector.js +2 -2
- package/dist/src/browser/web-ai/context-pack/file-selector.js.map +1 -1
- package/dist/src/browser/web-ai/context-pack/report.js +1 -1
- package/dist/src/browser/web-ai/context-pack/report.js.map +1 -1
- package/dist/src/browser/web-ai/copy-markdown.js +25 -5
- package/dist/src/browser/web-ai/copy-markdown.js.map +1 -1
- package/dist/src/browser/web-ai/diagnostics.js +17 -14
- package/dist/src/browser/web-ai/diagnostics.js.map +1 -1
- package/dist/src/browser/web-ai/doctor.js +10 -6
- package/dist/src/browser/web-ai/doctor.js.map +1 -1
- package/dist/src/browser/web-ai/dom-hash.js.map +1 -1
- package/dist/src/browser/web-ai/errors.js +5 -22
- package/dist/src/browser/web-ai/errors.js.map +1 -1
- package/dist/src/browser/web-ai/gemini-live.js +7 -6
- package/dist/src/browser/web-ai/gemini-live.js.map +1 -1
- package/dist/src/browser/web-ai/grok-live.js +9 -5
- package/dist/src/browser/web-ai/grok-live.js.map +1 -1
- package/dist/src/browser/web-ai/index.js +5 -0
- package/dist/src/browser/web-ai/index.js.map +1 -1
- package/dist/src/browser/web-ai/observation-bundle.js +90 -0
- package/dist/src/browser/web-ai/observation-bundle.js.map +1 -0
- package/dist/src/browser/web-ai/observe-actions.js +186 -0
- package/dist/src/browser/web-ai/observe-actions.js.map +1 -0
- package/dist/src/browser/web-ai/observe-targets.js +6 -1
- package/dist/src/browser/web-ai/observe-targets.js.map +1 -1
- package/dist/src/browser/web-ai/planner-contract.js +18 -0
- package/dist/src/browser/web-ai/planner-contract.js.map +1 -0
- package/dist/src/browser/web-ai/post-action-assert.js +8 -7
- package/dist/src/browser/web-ai/post-action-assert.js.map +1 -1
- package/dist/src/browser/web-ai/product-surfaces.js +2 -1
- package/dist/src/browser/web-ai/product-surfaces.js.map +1 -1
- package/dist/src/browser/web-ai/provider-adapter.js.map +1 -1
- package/dist/src/browser/web-ai/question.js +14 -7
- package/dist/src/browser/web-ai/question.js.map +1 -1
- package/dist/src/browser/web-ai/ref-registry.js.map +1 -1
- package/dist/src/browser/web-ai/self-heal.js +20 -14
- package/dist/src/browser/web-ai/self-heal.js.map +1 -1
- package/dist/src/browser/web-ai/session-store.js +61 -1
- package/dist/src/browser/web-ai/session-store.js.map +1 -1
- package/dist/src/browser/web-ai/session.js +43 -2
- package/dist/src/browser/web-ai/session.js.map +1 -1
- package/dist/src/browser/web-ai/source-audit.js +116 -0
- package/dist/src/browser/web-ai/source-audit.js.map +1 -0
- package/dist/src/browser/web-ai/tab-finalizer.js +18 -0
- package/dist/src/browser/web-ai/tab-finalizer.js.map +1 -0
- package/dist/src/browser/web-ai/tab-lease-store.js +390 -0
- package/dist/src/browser/web-ai/tab-lease-store.js.map +1 -0
- package/dist/src/browser/web-ai/tab-pool.js +44 -0
- package/dist/src/browser/web-ai/tab-pool.js.map +1 -0
- package/dist/src/browser/web-ai/target-resolver.js +31 -0
- package/dist/src/browser/web-ai/target-resolver.js.map +1 -0
- package/dist/src/browser/web-ai/trace-persistence.js +4 -2
- package/dist/src/browser/web-ai/trace-persistence.js.map +1 -1
- package/dist/src/browser/web-ai/vendor-editor-contract.js.map +1 -1
- package/dist/src/browser/web-ai/watcher.js +8 -7
- package/dist/src/browser/web-ai/watcher.js.map +1 -1
- package/dist/src/cli/acp-client.js +40 -26
- package/dist/src/cli/acp-client.js.map +1 -1
- package/dist/src/cli/command-context.js +3 -3
- package/dist/src/cli/command-context.js.map +1 -1
- package/dist/src/cli/commands.js +25 -16
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/compact.js.map +1 -1
- package/dist/src/cli/handlers-completions.js +6 -4
- package/dist/src/cli/handlers-completions.js.map +1 -1
- package/dist/src/cli/handlers-runtime.js +45 -38
- package/dist/src/cli/handlers-runtime.js.map +1 -1
- package/dist/src/cli/handlers.js +44 -36
- package/dist/src/cli/handlers.js.map +1 -1
- package/dist/src/cli/readiness.js +3 -1
- package/dist/src/cli/readiness.js.map +1 -1
- package/dist/src/cli/registry.js +1 -1
- package/dist/src/cli/registry.js.map +1 -1
- package/dist/src/cli/tui/overlay.js +4 -1
- package/dist/src/cli/tui/overlay.js.map +1 -1
- package/dist/src/cli/types.js +4 -0
- package/dist/src/cli/types.js.map +1 -0
- package/dist/src/command-contract/catalog.js +1 -1
- package/dist/src/command-contract/catalog.js.map +1 -1
- package/dist/src/command-contract/help-renderer.js +1 -1
- package/dist/src/command-contract/help-renderer.js.map +1 -1
- package/dist/src/core/browser-open-default.js +13 -0
- package/dist/src/core/browser-open-default.js.map +1 -0
- package/dist/src/core/browser-open.js +41 -0
- package/dist/src/core/browser-open.js.map +1 -0
- package/dist/src/core/bus.js +13 -2
- package/dist/src/core/bus.js.map +1 -1
- package/dist/src/core/compact.js +5 -4
- package/dist/src/core/compact.js.map +1 -1
- package/dist/src/core/config.js +51 -52
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/db.js +48 -6
- package/dist/src/core/db.js.map +1 -1
- package/dist/src/core/employees.js +9 -8
- package/dist/src/core/employees.js.map +1 -1
- package/dist/src/core/instance.js +7 -6
- package/dist/src/core/instance.js.map +1 -1
- package/dist/src/core/logger.js +1 -1
- package/dist/src/core/logger.js.map +1 -1
- package/dist/src/core/main-session.js +7 -7
- package/dist/src/core/main-session.js.map +1 -1
- package/dist/src/core/path-expand.js +10 -0
- package/dist/src/core/path-expand.js.map +1 -0
- package/dist/src/core/runtime-path.js +1 -1
- package/dist/src/core/runtime-path.js.map +1 -1
- package/dist/src/core/runtime-settings.js +13 -13
- package/dist/src/core/runtime-settings.js.map +1 -1
- package/dist/src/core/settings-merge.js +14 -14
- package/dist/src/core/settings-merge.js.map +1 -1
- package/dist/src/core/strip-undefined.js +13 -0
- package/dist/src/core/strip-undefined.js.map +1 -0
- package/dist/src/core/tcc.js +1 -1
- package/dist/src/core/tcc.js.map +1 -1
- package/dist/src/discord/bot.js +19 -18
- package/dist/src/discord/bot.js.map +1 -1
- package/dist/src/discord/channel-types.js +14 -0
- package/dist/src/discord/channel-types.js.map +1 -0
- package/dist/src/discord/commands.js +7 -6
- package/dist/src/discord/commands.js.map +1 -1
- package/dist/src/discord/discord-file.js.map +1 -1
- package/dist/src/discord/forwarder.js +3 -3
- package/dist/src/discord/forwarder.js.map +1 -1
- package/dist/src/http/error-middleware.js +3 -3
- package/dist/src/http/error-middleware.js.map +1 -1
- package/dist/src/http/response.js +2 -2
- package/dist/src/http/response.js.map +1 -1
- package/dist/src/ide/diff.js +7 -7
- package/dist/src/ide/diff.js.map +1 -1
- package/dist/src/manager/board/routes.js +28 -27
- package/dist/src/manager/board/routes.js.map +1 -1
- package/dist/src/manager/dashboard-home.js +3 -5
- package/dist/src/manager/dashboard-home.js.map +1 -1
- package/dist/src/manager/dashboard-service.js +2 -2
- package/dist/src/manager/dashboard-service.js.map +1 -1
- package/dist/src/manager/health-history.js +10 -9
- package/dist/src/manager/health-history.js.map +1 -1
- package/dist/src/manager/launchd-service.js +2 -2
- package/dist/src/manager/launchd-service.js.map +1 -1
- package/dist/src/manager/lifecycle-helpers.js +7 -6
- package/dist/src/manager/lifecycle-helpers.js.map +1 -1
- package/dist/src/manager/lifecycle.js +7 -6
- package/dist/src/manager/lifecycle.js.map +1 -1
- package/dist/src/manager/logs.js +18 -18
- package/dist/src/manager/logs.js.map +1 -1
- package/dist/src/manager/metadata.js +10 -2
- package/dist/src/manager/metadata.js.map +1 -1
- package/dist/src/manager/notes/assets.js +216 -0
- package/dist/src/manager/notes/assets.js.map +1 -0
- package/dist/src/manager/notes/capabilities.js +56 -0
- package/dist/src/manager/notes/capabilities.js.map +1 -0
- package/dist/src/manager/notes/constants.js +11 -0
- package/dist/src/manager/notes/constants.js.map +1 -0
- package/dist/src/manager/notes/frontmatter.js +120 -0
- package/dist/src/manager/notes/frontmatter.js.map +1 -0
- package/dist/src/manager/notes/link-resolver.js +96 -0
- package/dist/src/manager/notes/link-resolver.js.map +1 -0
- package/dist/src/manager/notes/path-guards.js +4 -0
- package/dist/src/manager/notes/path-guards.js.map +1 -1
- package/dist/src/manager/notes/remote-assets.js +128 -0
- package/dist/src/manager/notes/remote-assets.js.map +1 -0
- package/dist/src/manager/notes/routes.js +63 -13
- package/dist/src/manager/notes/routes.js.map +1 -1
- package/dist/src/manager/notes/store.js +3 -2
- package/dist/src/manager/notes/store.js.map +1 -1
- package/dist/src/manager/notes/system-trash.js +3 -3
- package/dist/src/manager/notes/system-trash.js.map +1 -1
- package/dist/src/manager/notes/vault-index.js +220 -0
- package/dist/src/manager/notes/vault-index.js.map +1 -0
- package/dist/src/manager/notes/watcher.js +27 -0
- package/dist/src/manager/notes/watcher.js.map +1 -0
- package/dist/src/manager/notes/wiki-links.js +109 -0
- package/dist/src/manager/notes/wiki-links.js.map +1 -0
- package/dist/src/manager/process-verify.js +29 -0
- package/dist/src/manager/process-verify.js.map +1 -1
- package/dist/src/manager/profiles.js +3 -2
- package/dist/src/manager/profiles.js.map +1 -1
- package/dist/src/manager/registry.js +60 -58
- package/dist/src/manager/registry.js.map +1 -1
- package/dist/src/manager/routes/electron-metrics.js +22 -22
- package/dist/src/manager/routes/electron-metrics.js.map +1 -1
- package/dist/src/manager/scan.js +2 -2
- package/dist/src/manager/scan.js.map +1 -1
- package/dist/src/manager/schedule/routes.js +24 -23
- package/dist/src/manager/schedule/routes.js.map +1 -1
- package/dist/src/manager/server.js +28 -28
- package/dist/src/manager/server.js.map +1 -1
- package/dist/src/manager/systemd-service.js +1 -1
- package/dist/src/manager/systemd-service.js.map +1 -1
- package/dist/src/memory/bootstrap.js +5 -4
- package/dist/src/memory/bootstrap.js.map +1 -1
- package/dist/src/memory/heartbeat-schedule.js +10 -10
- package/dist/src/memory/heartbeat-schedule.js.map +1 -1
- package/dist/src/memory/heartbeat.js +23 -22
- package/dist/src/memory/heartbeat.js.map +1 -1
- package/dist/src/memory/indexing.js.map +1 -1
- package/dist/src/memory/keyword-expand.js +1 -1
- package/dist/src/memory/keyword-expand.js.map +1 -1
- package/dist/src/memory/memory.js +2 -2
- package/dist/src/memory/memory.js.map +1 -1
- package/dist/src/memory/runtime.js +6 -5
- package/dist/src/memory/runtime.js.map +1 -1
- package/dist/src/memory/shared.js +2 -1
- package/dist/src/memory/shared.js.map +1 -1
- package/dist/src/memory/worklog.js +1 -1
- package/dist/src/memory/worklog.js.map +1 -1
- package/dist/src/messaging/runtime.js +13 -10
- package/dist/src/messaging/runtime.js.map +1 -1
- package/dist/src/messaging/send.js +16 -15
- package/dist/src/messaging/send.js.map +1 -1
- package/dist/src/orchestrator/collect.js +6 -6
- package/dist/src/orchestrator/collect.js.map +1 -1
- package/dist/src/orchestrator/distribute.js +112 -77
- package/dist/src/orchestrator/distribute.js.map +1 -1
- package/dist/src/orchestrator/gateway.js +6 -5
- package/dist/src/orchestrator/gateway.js.map +1 -1
- package/dist/src/orchestrator/pipeline.js +42 -42
- package/dist/src/orchestrator/pipeline.js.map +1 -1
- package/dist/src/orchestrator/state-machine.js +3 -3
- package/dist/src/orchestrator/state-machine.js.map +1 -1
- package/dist/src/orchestrator/worker-registry.js +4 -3
- package/dist/src/orchestrator/worker-registry.js.map +1 -1
- package/dist/src/prompt/builder.js +24 -19
- package/dist/src/prompt/builder.js.map +1 -1
- package/dist/src/prompt/templates/a1-system.md +15 -10
- package/dist/src/prompt/templates/control-system.md +9 -7
- package/dist/src/prompt/templates/employee.md +5 -4
- package/dist/src/prompt/templates/vision-click.md +1 -1
- package/dist/src/routes/_http-error.js +15 -0
- package/dist/src/routes/_http-error.js.map +1 -0
- package/dist/src/routes/avatar.js +6 -5
- package/dist/src/routes/avatar.js.map +1 -1
- package/dist/src/routes/browser.js +132 -45
- package/dist/src/routes/browser.js.map +1 -1
- package/dist/src/routes/employees.js +28 -21
- package/dist/src/routes/employees.js.map +1 -1
- package/dist/src/routes/heartbeat.js +12 -9
- package/dist/src/routes/heartbeat.js.map +1 -1
- package/dist/src/routes/i18n.js +13 -7
- package/dist/src/routes/i18n.js.map +1 -1
- package/dist/src/routes/jaw-memory.js +15 -11
- package/dist/src/routes/jaw-memory.js.map +1 -1
- package/dist/src/routes/memory.js +25 -20
- package/dist/src/routes/memory.js.map +1 -1
- package/dist/src/routes/messaging.js +50 -29
- package/dist/src/routes/messaging.js.map +1 -1
- package/dist/src/routes/orchestrate.js +38 -24
- package/dist/src/routes/orchestrate.js.map +1 -1
- package/dist/src/routes/quota.js +81 -17
- package/dist/src/routes/quota.js.map +1 -1
- package/dist/src/routes/settings.js +28 -18
- package/dist/src/routes/settings.js.map +1 -1
- package/dist/src/routes/skills.js +22 -13
- package/dist/src/routes/skills.js.map +1 -1
- package/dist/src/routes/traces.js +77 -0
- package/dist/src/routes/traces.js.map +1 -0
- package/dist/src/security/path-guards.js +2 -1
- package/dist/src/security/path-guards.js.map +1 -1
- package/dist/src/shared/tool-log-sanitize.js +191 -0
- package/dist/src/shared/tool-log-sanitize.js.map +1 -0
- package/dist/src/telegram/bot.js +65 -55
- package/dist/src/telegram/bot.js.map +1 -1
- package/dist/src/telegram/forwarder.js +5 -9
- package/dist/src/telegram/forwarder.js.map +1 -1
- package/dist/src/telegram/telegram-file.js +28 -24
- package/dist/src/telegram/telegram-file.js.map +1 -1
- package/dist/src/telegram/voice.js +4 -3
- package/dist/src/telegram/voice.js.map +1 -1
- package/dist/src/trace/redact.js +50 -0
- package/dist/src/trace/redact.js.map +1 -0
- package/dist/src/trace/store.js +162 -0
- package/dist/src/trace/store.js.map +1 -0
- package/dist/src/trace/types.js +2 -0
- package/dist/src/trace/types.js.map +1 -0
- package/dist/src/types/cli-engine.js +34 -0
- package/dist/src/types/cli-engine.js.map +1 -0
- package/dist/src/types/cli-events.js +31 -0
- package/dist/src/types/cli-events.js.map +1 -0
- package/package.json +22 -2
- package/public/css/tool-ui.css +3 -1
- package/public/css/trace-drawer.css +48 -0
- package/public/dist/assets/{AdvancedExport-3WAYIabE.js → AdvancedExport-DJZ2VmBR.js} +1 -1
- package/public/dist/assets/Agent-CgpLT8IY.js +1 -0
- package/public/dist/assets/Browser-CrkiQoB8.js +1 -0
- package/public/dist/assets/{ChannelsDiscord-UzFPlWT4.js → ChannelsDiscord-CVUC22D4.js} +1 -1
- package/public/dist/assets/{ChannelsTelegram-DNWtPX0w.js → ChannelsTelegram-DEatIQNM.js} +1 -1
- package/public/dist/assets/{DashboardMeta-Y_6nVeJO.js → DashboardMeta-BKoxRc7i.js} +1 -1
- package/public/dist/assets/{Display-D8vGOl4s.js → Display-DnNGV9Km.js} +1 -1
- package/public/dist/assets/{Employees-YR_sIRK4.js → Employees-DZ2iJYKy.js} +1 -1
- package/public/dist/assets/{HealthBadge-CPePajyU.js → HealthBadge-Cq2c7G9s.js} +1 -1
- package/public/dist/assets/{Heartbeat-DTpAULQR.js → Heartbeat-BML6eTXZ.js} +1 -1
- package/public/dist/assets/{Mcp-DM-PgG6z.js → Mcp-BlEviQ3h.js} +1 -1
- package/public/dist/assets/{Memory-C_LvJnkn.js → Memory-BRyH80He.js} +1 -1
- package/public/dist/assets/MilkdownWysiwygEditor-Cm3uXfWf.js +52 -0
- package/public/dist/assets/ModelProvider-DxyR7EL9.js +1 -0
- package/public/dist/assets/Network-DDOOESh1.js +1 -0
- package/public/dist/assets/{Permissions-B1naJjjw.js → Permissions-Br0eSbKb.js} +1 -1
- package/public/dist/assets/{Permissions-BKffrMJD.js → Permissions-QHkzStqQ.js} +1 -1
- package/public/dist/assets/{Profile-DIqjSe2C.js → Profile-C79NKumk.js} +1 -1
- package/public/dist/assets/{Prompts-BMfbV6Y4.js → Prompts-BmiIDiXW.js} +1 -1
- package/public/dist/assets/{SpeechKeys-DjiQTzSL.js → SpeechKeys-B8304XJK.js} +1 -1
- package/public/dist/assets/app-Be58Cs3Y.js +32 -0
- package/public/dist/assets/{app-BzvwJJiv.css → app-CphocJzo.css} +1 -1
- package/public/dist/assets/{dist-DTvxN3ux.js → dist-BD0UXfgF2.js} +1 -1
- package/public/dist/assets/{dist-CASeq-Tl.js → dist-BNfXO3Yr.js} +1 -1
- package/public/dist/assets/{dist-BMPiaUzF.js → dist-BUnPYbK3.js} +1 -1
- package/public/dist/assets/{dist-CT_X3hVT.js → dist-BZosRD2u.js} +1 -1
- package/public/dist/assets/{dist-4J6YbNXv.js → dist-Bd9PlnQm.js} +1 -1
- package/public/dist/assets/{dist-BcZjyn5g.js → dist-BsT5UdNP.js} +1 -1
- package/public/dist/assets/{dist-BfBhOPR-.js → dist-CIuXW-sc.js} +1 -1
- package/public/dist/assets/{dist-BMiCig3A2.js → dist-CL4PTYWf.js} +1 -1
- package/public/dist/assets/{dist-VyP6-HLb.js → dist-Ch5VAlV9.js} +1 -1
- package/public/dist/assets/dist-ClqO40BE.js +1 -0
- package/public/dist/assets/{dist-c98Tp7bP.js → dist-Cp42cMcI.js} +1 -1
- package/public/dist/assets/{dist-CIlYL1qe.js → dist-CpUK8ypo.js} +1 -1
- package/public/dist/assets/dist-CxeLAw2Y.js +1 -0
- package/public/dist/assets/dist-D2SH8nxa.js +1 -0
- package/public/dist/assets/{dist-84fwQ7bO.js → dist-D6cUXP7K.js} +1 -1
- package/public/dist/assets/{dist-BOCcQAyF.js → dist-D7bCuS3f.js} +1 -1
- package/public/dist/assets/{dist-DMmpfLVP.js → dist-DFYRUAjN.js} +1 -1
- package/public/dist/assets/{dist-DdY6pTJr.js → dist-DZsFVYF4.js} +1 -1
- package/public/dist/assets/{dist-B0p3Eyme.js → dist-Db16ogVk.js} +1 -1
- package/public/dist/assets/{dist-DlnNtr6L.js → dist-DfodGES_.js} +1 -1
- package/public/dist/assets/{dist-DO9so2a2.js → dist-SU-YTAIg.js} +1 -1
- package/public/dist/assets/{dist-DUjXiMLP.js → dist-UYn7T-GH.js} +1 -1
- package/public/dist/assets/{dist-BW1409rz.js → dist-W51oDoeA.js} +1 -1
- package/public/dist/assets/{dist-BimBQZx1.js → dist-eU7TyC86.js} +1 -1
- package/public/dist/assets/dist-l9HH00ip.js +1 -0
- package/public/dist/assets/{dist-BrOPNxdH.js → dist-urPTQzXL.js} +1 -1
- package/public/dist/assets/{dist-AloEV3J52.js → dist-yHP6L0Ty.js} +1 -1
- package/public/dist/assets/{employees-Bbabvbyx.js → employees-CxdghzoD.js} +7 -7
- package/public/dist/assets/{error-normalize-DdvKGLt_.js → error-normalize-5n-zlEQ3.js} +1 -1
- package/public/dist/assets/insert-image-markdown-DIEa-zjk.js +22 -0
- package/public/dist/assets/{manager-DyB2ZUr9.css → manager-DEiyrWDP.css} +1 -1
- package/public/dist/assets/manager-UEXd1_9T.js +25 -0
- package/public/dist/assets/{memory-B_plvcuQ.js → memory-CsMNkYtv.js} +9 -9
- package/public/dist/assets/memory-DXad_DPO.js +1 -0
- package/public/dist/assets/{page-shell-DML_HneX.js → page-shell-D5tbivHH.js} +1 -1
- package/public/dist/assets/{render-DEhbfUAW.js → render-DGQX46ei.js} +2 -2
- package/public/dist/assets/{settings-D7F-_kYG.js → settings-BH213Yv3.js} +14 -14
- package/public/dist/assets/settings-DXT87G2U.js +1 -0
- package/public/dist/assets/settings-client-ajlwI-oK.js +1 -0
- package/public/dist/assets/skills-5o_1v0nz.js +1 -0
- package/public/dist/assets/{skills-DoGJOc0D.js → skills-CQtCtHPA.js} +6 -6
- package/public/dist/assets/slash-commands-D4-hrrmh.js +1 -0
- package/public/dist/assets/{slash-commands-khNFPOyF.js → slash-commands-Dzk1xHWS.js} +1 -1
- package/public/dist/assets/trace-drawer-SRKcfm2S.js +15 -0
- package/public/dist/assets/ui-CdRKN2S6.js +141 -0
- package/public/dist/assets/ui-n43jmg_f.js +1 -0
- package/public/dist/assets/{ws-B2aJ-nD2.js → ws-CTHQFzM1.js} +8 -8
- package/public/dist/index.html +2 -2
- package/public/dist/manager/index.html +2 -2
- package/public/index.html +1 -0
- package/public/js/constants.ts +5 -5
- package/public/js/diagram/iframe-renderer.ts +11 -11
- package/public/js/features/chat.ts +1 -1
- package/public/js/features/memory.ts +10 -10
- package/public/js/features/pending-queue.ts +2 -2
- package/public/js/features/process-block.ts +257 -29
- package/public/js/features/process-step-match.ts +18 -0
- package/public/js/features/settings-cli-status.ts +1 -1
- package/public/js/features/settings-stt.ts +37 -11
- package/public/js/features/settings-templates.ts +2 -2
- package/public/js/features/slash-commands.ts +1 -1
- package/public/js/features/tool-ui.ts +13 -7
- package/public/js/features/trace-drawer.ts +122 -0
- package/public/js/icons.ts +1 -1
- package/public/js/main.ts +28 -28
- package/public/js/provider-icons.ts +1 -1
- package/public/js/render.ts +29 -27
- package/public/js/sanitizer.ts +4 -0
- package/public/js/ui.ts +234 -68
- package/public/js/virtual-scroll.ts +19 -33
- package/public/js/ws.ts +22 -10
- package/public/manager/src/App.tsx +34 -6
- package/public/manager/src/api.ts +55 -1
- package/public/manager/src/components/ActivityTimeline.tsx +1 -1
- package/public/manager/src/components/InstanceDetailPanel.tsx +2 -2
- package/public/manager/src/components/InstanceGroups.tsx +5 -5
- package/public/manager/src/components/InstanceListContent.tsx +1 -1
- package/public/manager/src/dashboard-features.ts +1 -1
- package/public/manager/src/manager-notes.css +49 -2
- package/public/manager/src/notes/MarkdownEditor.tsx +7 -3
- package/public/manager/src/notes/NotesFileTree.tsx +45 -6
- package/public/manager/src/notes/NotesSidebar.tsx +24 -55
- package/public/manager/src/notes/NotesWorkspace.tsx +50 -3
- package/public/manager/src/notes/image-assets/clipboard-images.ts +100 -0
- package/public/manager/src/notes/image-assets/insert-image-markdown.ts +85 -0
- package/public/manager/src/notes/notes-api.ts +2 -0
- package/public/manager/src/notes/notes-types.ts +8 -1
- package/public/manager/src/notes/rendering/MarkdownRenderer.tsx +6 -0
- package/public/manager/src/notes/rendering/markdown-render-security.ts +19 -1
- package/public/manager/src/notes/rich-markdown/paste-policy.ts +6 -2
- package/public/manager/src/notes/rich-markdown/rich-markdown-extension.ts +5 -5
- package/public/manager/src/notes/rich-markdown/rich-widget.ts +8 -8
- package/public/manager/src/notes/useNotesExternalSync.ts +37 -0
- package/public/manager/src/notes/useNotesModel.ts +91 -0
- package/public/manager/src/notes/wysiwyg/MilkdownWysiwygEditor.tsx +157 -17
- package/public/manager/src/notes/wysiwyg/milkdown-block-keymap.ts +14 -2
- package/public/manager/src/notes/wysiwyg/milkdown-code-block-view.ts +20 -20
- package/public/manager/src/notes/wysiwyg/milkdown-heading-source-view.ts +8 -8
- package/public/manager/src/notes/wysiwyg/milkdown-math.ts +25 -25
- package/public/manager/src/settings/pages/Browser.tsx +38 -20
- package/public/manager/src/settings/pages/ModelProvider.tsx +11 -9
- package/public/manager/src/settings/pages/Network.tsx +10 -10
- package/public/manager/src/settings/pages/components/HealthBadge.tsx +12 -12
- package/public/manager/src/settings/pages/components/agent/runtime-employees-helpers.ts +12 -10
- package/public/manager/src/settings/pages/components/employees-helpers.ts +9 -9
- package/public/manager/src/settings/pages/components/heartbeat-helpers.ts +13 -13
- package/public/manager/src/settings/pages/components/memory-helpers.ts +4 -4
- package/public/manager/src/settings/pages/mcp-helpers.ts +7 -7
- package/public/manager/src/settings/settings-client.ts +4 -3
- package/public/manager/src/sync/IframeBridge.tsx +29 -0
- package/public/manager/src/sync/VisibilityBridge.tsx +24 -0
- package/public/manager/src/sync/invalidation-bus.ts +30 -0
- package/public/manager/src/sync/useInvalidationSubscription.ts +19 -0
- package/public/manager/src/types.ts +101 -0
- package/scripts/check-strict-baseline.mjs +255 -0
- package/scripts/claim-audit.mjs +159 -0
- package/scripts/i18n-registry.ts +4 -4
- package/scripts/install-officecli.sh +2 -2
- package/scripts/install-wsl.sh +2 -1
- package/scripts/release-gates.mjs +347 -0
- package/scripts/release-preview.sh +3 -0
- package/scripts/release.sh +4 -0
- package/scripts/smoke/opencode-external-dir-smoke.ts +14 -5
- package/public/dist/assets/Agent-BYdzZwD0.js +0 -1
- package/public/dist/assets/Browser-CkGeczuN.js +0 -1
- package/public/dist/assets/MilkdownWysiwygEditor-B7k9bAey.js +0 -52
- package/public/dist/assets/ModelProvider-CX3Qhowu.js +0 -1
- package/public/dist/assets/Network-DfPLFAvw.js +0 -1
- package/public/dist/assets/app-DLYoRkU9.js +0 -32
- package/public/dist/assets/dist-8zNAQAIa.js +0 -1
- package/public/dist/assets/dist-BgeY6nvK.js +0 -1
- package/public/dist/assets/dist-BzDGGxHQ.js +0 -1
- package/public/dist/assets/dist-D3YKbVi-.js +0 -1
- package/public/dist/assets/manager-CUSgFbMO.js +0 -25
- package/public/dist/assets/markdown-render-security-DJfJPWO-.js +0 -22
- package/public/dist/assets/memory-DQ6dU0qs.js +0 -1
- package/public/dist/assets/settings-DxLPUbpj.js +0 -1
- package/public/dist/assets/settings-client-CGf3uPPf.js +0 -1
- package/public/dist/assets/skills-yMfNYJ8m.js +0 -1
- package/public/dist/assets/slash-commands-CZcwr1W6.js +0 -1
- package/public/dist/assets/ui-04YlOMgn.js +0 -136
- package/public/dist/assets/ui-D6hlMjRq.js +0 -1
- /package/public/dist/assets/{InlineWarn-CqgWEC41.js → InlineWarn-BooBRm7o.js} +0 -0
- /package/public/dist/assets/{agent-meta-puNn13DV.js → agent-meta-DHddpWHQ.js} +0 -0
- /package/public/dist/assets/{fields-DH9JS3mb.js → fields-BtncIZYA.js} +0 -0
- /package/public/dist/assets/{mermaid-loader-6XC0y10y.js → mermaid-loader-BEFIOoJn.js} +0 -0
- /package/public/dist/assets/{path-utils-CtsDDGZg.js → path-utils-BuEEtj9w.js} +0 -0
- /package/public/dist/assets/{w3c-keyname-VSld09PZ.js → w3c-keyname-IiiZScED.js} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState, type CSSProperties, type DragEvent, type KeyboardEvent, type MouseEvent } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState, type CSSProperties, type DragEvent, type KeyboardEvent, type MouseEvent } from 'react';
|
|
2
2
|
import type { NotesTreeEntry } from './notes-types';
|
|
3
3
|
|
|
4
4
|
type NotesTrashItem = { path: string; kind: NotesTreeEntry['kind'] };
|
|
@@ -122,6 +122,12 @@ function buildTrashItems(
|
|
|
122
122
|
return items;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
function isEditableShortcutTarget(target: EventTarget | null): boolean {
|
|
126
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
127
|
+
const tag = target.tagName.toLowerCase();
|
|
128
|
+
return target.isContentEditable || tag === 'input' || tag === 'textarea' || tag === 'select';
|
|
129
|
+
}
|
|
130
|
+
|
|
125
131
|
function rangeSelect(flatPaths: string[], anchor: string, target: string): Set<string> {
|
|
126
132
|
const anchorIndex = flatPaths.indexOf(anchor);
|
|
127
133
|
const targetIndex = flatPaths.indexOf(target);
|
|
@@ -324,6 +330,7 @@ export function NotesFileTree(props: NotesFileTreeProps) {
|
|
|
324
330
|
const [dropTargetPath, setDropTargetPath] = useState<string | null>(null);
|
|
325
331
|
const [multiSelected, setMultiSelected] = useState<Set<string>>(() => new Set());
|
|
326
332
|
const [anchorPath, setAnchorPath] = useState<string | null>(null);
|
|
333
|
+
const [activeTreePath, setActiveTreePath] = useState<string | null>(null);
|
|
327
334
|
|
|
328
335
|
function toggleFolder(path: string): void {
|
|
329
336
|
setExpandedFolders(current => {
|
|
@@ -335,11 +342,11 @@ export function NotesFileTree(props: NotesFileTreeProps) {
|
|
|
335
342
|
}
|
|
336
343
|
|
|
337
344
|
const flatPaths = flattenEntries(props.entries, expandedFolders);
|
|
338
|
-
const pathKindLookup = (() => {
|
|
345
|
+
const pathKindLookup = useMemo(() => {
|
|
339
346
|
const map = new Map<string, NotesTreeEntry['kind']>();
|
|
340
347
|
collectPathKinds(props.entries, map);
|
|
341
348
|
return map;
|
|
342
|
-
})
|
|
349
|
+
}, [props.entries]);
|
|
343
350
|
|
|
344
351
|
function trashSelected(): void {
|
|
345
352
|
if (multiSelected.size === 0) return;
|
|
@@ -349,6 +356,7 @@ export function NotesFileTree(props: NotesFileTreeProps) {
|
|
|
349
356
|
}
|
|
350
357
|
|
|
351
358
|
const onEntryClick = useCallback((path: string, event: MouseEvent) => {
|
|
359
|
+
setActiveTreePath(path);
|
|
352
360
|
if (event.shiftKey && anchorPath) {
|
|
353
361
|
const range = rangeSelect(flatPaths, anchorPath, path);
|
|
354
362
|
setMultiSelected(range);
|
|
@@ -374,6 +382,9 @@ export function NotesFileTree(props: NotesFileTreeProps) {
|
|
|
374
382
|
}, [props.selectedPath]);
|
|
375
383
|
|
|
376
384
|
useEffect(() => {
|
|
385
|
+
if (activeTreePath && !pathKindLookup.has(activeTreePath)) {
|
|
386
|
+
setActiveTreePath(null);
|
|
387
|
+
}
|
|
377
388
|
if (multiSelected.size === 0) return;
|
|
378
389
|
setMultiSelected(prev => {
|
|
379
390
|
let changed = false;
|
|
@@ -384,17 +395,20 @@ export function NotesFileTree(props: NotesFileTreeProps) {
|
|
|
384
395
|
}
|
|
385
396
|
return changed ? next : prev;
|
|
386
397
|
});
|
|
387
|
-
}, [
|
|
398
|
+
}, [activeTreePath, multiSelected.size, pathKindLookup]);
|
|
388
399
|
|
|
389
400
|
useEffect(() => {
|
|
390
401
|
function handleCopyPath(event: globalThis.KeyboardEvent): void {
|
|
391
402
|
if (!(event.metaKey || event.ctrlKey) || !event.shiftKey || event.key.toLowerCase() !== 'c') return;
|
|
403
|
+
if (isEditableShortcutTarget(event.target)) return;
|
|
392
404
|
const root = props.notesRoot;
|
|
393
405
|
if (!root) return;
|
|
394
406
|
|
|
395
407
|
const paths = multiSelected.size > 0
|
|
396
408
|
? Array.from(multiSelected)
|
|
397
|
-
:
|
|
409
|
+
: activeTreePath && pathKindLookup.has(activeTreePath)
|
|
410
|
+
? [activeTreePath]
|
|
411
|
+
: props.selectedPath ? [props.selectedPath] : [];
|
|
398
412
|
if (paths.length === 0) return;
|
|
399
413
|
|
|
400
414
|
event.preventDefault();
|
|
@@ -404,7 +418,32 @@ export function NotesFileTree(props: NotesFileTreeProps) {
|
|
|
404
418
|
|
|
405
419
|
window.addEventListener('keydown', handleCopyPath);
|
|
406
420
|
return () => window.removeEventListener('keydown', handleCopyPath);
|
|
407
|
-
}, [multiSelected, props.selectedPath, props.notesRoot]);
|
|
421
|
+
}, [activeTreePath, multiSelected, pathKindLookup, props.selectedPath, props.notesRoot]);
|
|
422
|
+
|
|
423
|
+
useEffect(() => {
|
|
424
|
+
function handleCommandTrash(event: globalThis.KeyboardEvent): void {
|
|
425
|
+
if (event.defaultPrevented) return;
|
|
426
|
+
if (!(event.metaKey || event.ctrlKey)) return;
|
|
427
|
+
if (event.key !== 'Delete' && event.key !== 'Backspace') return;
|
|
428
|
+
if (isEditableShortcutTarget(event.target)) return;
|
|
429
|
+
|
|
430
|
+
const items = multiSelected.size > 0
|
|
431
|
+
? buildTrashItems(multiSelected, pathKindLookup)
|
|
432
|
+
: props.selectedFolderPath && pathKindLookup.get(props.selectedFolderPath) === 'folder'
|
|
433
|
+
? [{ path: props.selectedFolderPath, kind: 'folder' as const }]
|
|
434
|
+
: props.selectedPath && pathKindLookup.get(props.selectedPath) === 'file'
|
|
435
|
+
? [{ path: props.selectedPath, kind: 'file' as const }]
|
|
436
|
+
: [];
|
|
437
|
+
if (items.length === 0) return;
|
|
438
|
+
|
|
439
|
+
event.preventDefault();
|
|
440
|
+
if (items.length > 1) props.onTrashPaths(items);
|
|
441
|
+
else props.onTrashPath(items[0].path, items[0].kind);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
window.addEventListener('keydown', handleCommandTrash);
|
|
445
|
+
return () => window.removeEventListener('keydown', handleCommandTrash);
|
|
446
|
+
}, [multiSelected, pathKindLookup, props]);
|
|
408
447
|
|
|
409
448
|
return (
|
|
410
449
|
<aside
|
|
@@ -1,32 +1,21 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
|
-
import { createNoteFile, createNoteFolder,
|
|
2
|
+
import { createNoteFile, createNoteFolder, renameNotePath, trashNotePath } from './notes-api';
|
|
3
3
|
import { NotesFileTree } from './NotesFileTree';
|
|
4
|
+
import { publishInvalidation } from '../sync/invalidation-bus';
|
|
4
5
|
import type { NotesTreeEntry } from './notes-types';
|
|
5
6
|
|
|
6
7
|
type NotesSidebarProps = {
|
|
8
|
+
tree: NotesTreeEntry[];
|
|
9
|
+
loading: boolean;
|
|
10
|
+
error: string | null;
|
|
11
|
+
notesRoot: string | null;
|
|
7
12
|
selectedPath: string | null;
|
|
8
13
|
dirtyPath: string | null;
|
|
9
14
|
treeWidth: number;
|
|
10
15
|
onSelectedPathChange: (path: string | null) => void;
|
|
16
|
+
onRefreshTree: (selectPath?: string | null) => Promise<void>;
|
|
11
17
|
};
|
|
12
18
|
|
|
13
|
-
function firstFile(entries: NotesTreeEntry[]): string | null {
|
|
14
|
-
for (const entry of entries) {
|
|
15
|
-
if (entry.kind === 'file') return entry.path;
|
|
16
|
-
const child = firstFile(entry.children || []);
|
|
17
|
-
if (child) return child;
|
|
18
|
-
}
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function hasFile(entries: NotesTreeEntry[], path: string): boolean {
|
|
23
|
-
for (const entry of entries) {
|
|
24
|
-
if (entry.kind === 'file' && entry.path === path) return true;
|
|
25
|
-
if (hasFile(entry.children || [], path)) return true;
|
|
26
|
-
}
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
19
|
function movePathToFolder(path: string, folderPath: string | null): string {
|
|
31
20
|
const parts = path.split('/').filter(Boolean);
|
|
32
21
|
const name = parts[parts.length - 1];
|
|
@@ -87,35 +76,9 @@ function batchTrashConfirmMessage(items: { path: string; kind: NotesTreeEntry['k
|
|
|
87
76
|
}
|
|
88
77
|
|
|
89
78
|
export function NotesSidebar(props: NotesSidebarProps) {
|
|
90
|
-
const [tree, setTree] = useState<NotesTreeEntry[]>([]);
|
|
91
|
-
const [loading, setLoading] = useState(false);
|
|
92
79
|
const [error, setError] = useState<string | null>(null);
|
|
93
80
|
const [status, setStatus] = useState<string | null>(null);
|
|
94
81
|
const [selectedFolderPath, setSelectedFolderPath] = useState<string | null>(null);
|
|
95
|
-
const [notesRoot, setNotesRoot] = useState<string | null>(null);
|
|
96
|
-
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
void fetchNotesInfo().then(info => setNotesRoot(info.root)).catch(() => {});
|
|
99
|
-
}, []);
|
|
100
|
-
|
|
101
|
-
async function refreshTree(selectPath = props.selectedPath): Promise<void> {
|
|
102
|
-
setLoading(true);
|
|
103
|
-
setError(null);
|
|
104
|
-
try {
|
|
105
|
-
const next = await fetchNotesTree();
|
|
106
|
-
setTree(next);
|
|
107
|
-
const nextSelected = selectPath && hasFile(next, selectPath) ? selectPath : firstFile(next);
|
|
108
|
-
if (nextSelected !== props.selectedPath) props.onSelectedPathChange(nextSelected);
|
|
109
|
-
} catch (err) {
|
|
110
|
-
setError((err as Error).message);
|
|
111
|
-
} finally {
|
|
112
|
-
setLoading(false);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
useEffect(() => {
|
|
117
|
-
void refreshTree();
|
|
118
|
-
}, []);
|
|
119
82
|
|
|
120
83
|
async function createNote(): Promise<void> {
|
|
121
84
|
const fallback = selectedFolderPath ? `${selectedFolderPath}/untitled.md` : 'untitled.md';
|
|
@@ -125,7 +88,8 @@ export function NotesSidebar(props: NotesSidebarProps) {
|
|
|
125
88
|
setStatus(null);
|
|
126
89
|
const created = await createNoteFile(name.endsWith('.md') ? name : `${name}.md`, '');
|
|
127
90
|
props.onSelectedPathChange(created.path);
|
|
128
|
-
await
|
|
91
|
+
await props.onRefreshTree(created.path);
|
|
92
|
+
publishInvalidation({ topics: ['notes'], reason: 'note:created', source: 'ui', sourceId: 'notes-sidebar' });
|
|
129
93
|
} catch (err) {
|
|
130
94
|
setError((err as Error).message);
|
|
131
95
|
}
|
|
@@ -150,7 +114,8 @@ export function NotesSidebar(props: NotesSidebarProps) {
|
|
|
150
114
|
setStatus(null);
|
|
151
115
|
const created = await createNoteFolder(name);
|
|
152
116
|
setSelectedFolderPath(created.path);
|
|
153
|
-
await
|
|
117
|
+
await props.onRefreshTree();
|
|
118
|
+
publishInvalidation({ topics: ['notes'], reason: 'folder:created', source: 'ui', sourceId: 'notes-sidebar' });
|
|
154
119
|
} catch (err) {
|
|
155
120
|
setError((err as Error).message);
|
|
156
121
|
}
|
|
@@ -163,7 +128,8 @@ export function NotesSidebar(props: NotesSidebarProps) {
|
|
|
163
128
|
setStatus(null);
|
|
164
129
|
const moved = await renameNotePath(from, to);
|
|
165
130
|
if (props.selectedPath === from) props.onSelectedPathChange(moved.to);
|
|
166
|
-
await
|
|
131
|
+
await props.onRefreshTree(moved.to);
|
|
132
|
+
publishInvalidation({ topics: ['notes'], reason: 'note:moved', source: 'ui', sourceId: 'notes-sidebar' });
|
|
167
133
|
} catch (err) {
|
|
168
134
|
setError((err as Error).message);
|
|
169
135
|
}
|
|
@@ -182,7 +148,8 @@ export function NotesSidebar(props: NotesSidebarProps) {
|
|
|
182
148
|
const nextSelectedFolderPath = rebasePath(selectedFolderPath, renamed.from, renamed.to);
|
|
183
149
|
if (nextSelectedFolderPath !== selectedFolderPath) setSelectedFolderPath(nextSelectedFolderPath);
|
|
184
150
|
if (nextSelectedPath !== props.selectedPath) props.onSelectedPathChange(nextSelectedPath);
|
|
185
|
-
await
|
|
151
|
+
await props.onRefreshTree(nextSelectedPath);
|
|
152
|
+
publishInvalidation({ topics: ['notes'], reason: 'note:renamed', source: 'ui', sourceId: 'notes-sidebar' });
|
|
186
153
|
} catch (err) {
|
|
187
154
|
setError((err as Error).message);
|
|
188
155
|
}
|
|
@@ -232,7 +199,8 @@ export function NotesSidebar(props: NotesSidebarProps) {
|
|
|
232
199
|
setStatus(`Moved ${succeeded} item${succeeded === 1 ? '' : 's'} to trash.`);
|
|
233
200
|
}
|
|
234
201
|
|
|
235
|
-
await
|
|
202
|
+
await props.onRefreshTree(selectedCleared ? null : props.selectedPath);
|
|
203
|
+
publishInvalidation({ topics: ['notes'], reason: 'notes:batch-trashed', source: 'ui', sourceId: 'notes-sidebar' });
|
|
236
204
|
}
|
|
237
205
|
|
|
238
206
|
async function trashPath(path: string, kind: NotesTreeEntry['kind']): Promise<void> {
|
|
@@ -256,7 +224,8 @@ export function NotesSidebar(props: NotesSidebarProps) {
|
|
|
256
224
|
? ` Restore from: ${result.restoreHint}`
|
|
257
225
|
: '';
|
|
258
226
|
setStatus(`Moved ${result.path} to ${destination}.${restoreHint}`);
|
|
259
|
-
await
|
|
227
|
+
await props.onRefreshTree(selectedWasInside ? null : props.selectedPath);
|
|
228
|
+
publishInvalidation({ topics: ['notes'], reason: 'note:trashed', source: 'ui', sourceId: 'notes-sidebar' });
|
|
260
229
|
} catch (err) {
|
|
261
230
|
setError((err as Error).message);
|
|
262
231
|
}
|
|
@@ -264,16 +233,16 @@ export function NotesSidebar(props: NotesSidebarProps) {
|
|
|
264
233
|
|
|
265
234
|
return (
|
|
266
235
|
<>
|
|
267
|
-
{error && <section className="state error-state">{error}</section>}
|
|
236
|
+
{(props.error || error) && <section className="state error-state">{props.error || error}</section>}
|
|
268
237
|
{status && <section className="state notes-status-state">{status}</section>}
|
|
269
238
|
<NotesFileTree
|
|
270
|
-
entries={tree}
|
|
239
|
+
entries={props.tree}
|
|
271
240
|
selectedPath={props.selectedPath}
|
|
272
241
|
selectedFolderPath={selectedFolderPath}
|
|
273
242
|
dirtyPath={props.dirtyPath}
|
|
274
|
-
loading={loading}
|
|
243
|
+
loading={props.loading}
|
|
275
244
|
width={props.treeWidth}
|
|
276
|
-
notesRoot={notesRoot}
|
|
245
|
+
notesRoot={props.notesRoot}
|
|
277
246
|
onSelectPath={props.onSelectedPathChange}
|
|
278
247
|
onSelectFolder={setSelectedFolderPath}
|
|
279
248
|
onMovePath={(from, toFolder) => void moveNote(from, toFolder)}
|
|
@@ -282,7 +251,7 @@ export function NotesSidebar(props: NotesSidebarProps) {
|
|
|
282
251
|
onTrashPaths={items => void trashPaths(items)}
|
|
283
252
|
onCreateNote={() => void createNote()}
|
|
284
253
|
onCreateFolder={() => void createFolder()}
|
|
285
|
-
onRefresh={() => void
|
|
254
|
+
onRefresh={() => void props.onRefreshTree()}
|
|
286
255
|
/>
|
|
287
256
|
</>
|
|
288
257
|
);
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
2
|
import { MarkdownEditor } from './MarkdownEditor';
|
|
3
3
|
import { MarkdownPreview } from './MarkdownPreview';
|
|
4
4
|
import { NotesEmptyState } from './NotesEmptyState';
|
|
5
5
|
import { NotesToolbar } from './NotesToolbar';
|
|
6
|
+
import { renameNotePath } from './notes-api';
|
|
6
7
|
import { useNoteDocument } from './useNoteDocument';
|
|
7
|
-
import
|
|
8
|
+
import { publishInvalidation } from '../sync/invalidation-bus';
|
|
9
|
+
import type { NotesAuthoringMode, NotesVaultIndexSnapshot, NotesViewMode } from './notes-types';
|
|
8
10
|
|
|
9
11
|
type NotesPrimaryMode = 'raw' | 'preview' | 'wysiwyg';
|
|
10
12
|
|
|
11
13
|
type NotesWorkspaceProps = {
|
|
12
14
|
active: boolean;
|
|
13
15
|
selectedPath: string | null;
|
|
16
|
+
vaultIndex: NotesVaultIndexSnapshot | null;
|
|
14
17
|
viewMode: NotesViewMode;
|
|
15
18
|
authoringMode: NotesAuthoringMode;
|
|
16
19
|
wordWrap: boolean;
|
|
@@ -24,6 +27,12 @@ type NotesWorkspaceProps = {
|
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
const PRIMARY_MODE_CYCLE: NotesPrimaryMode[] = ['raw', 'preview', 'wysiwyg'];
|
|
30
|
+
const INVALID_TITLE_CHARS = /[/\\]/g;
|
|
31
|
+
|
|
32
|
+
function titleFromPath(path: string): string {
|
|
33
|
+
const name = path.split('/').pop() ?? path;
|
|
34
|
+
return name.endsWith('.md') ? name.slice(0, -3) : name;
|
|
35
|
+
}
|
|
27
36
|
|
|
28
37
|
function primaryModeFor(viewMode: NotesViewMode, authoringMode: NotesAuthoringMode): NotesPrimaryMode {
|
|
29
38
|
if (viewMode === 'preview') return 'preview';
|
|
@@ -33,6 +42,7 @@ function primaryModeFor(viewMode: NotesViewMode, authoringMode: NotesAuthoringMo
|
|
|
33
42
|
|
|
34
43
|
export function NotesWorkspace(props: NotesWorkspaceProps) {
|
|
35
44
|
const document = useNoteDocument();
|
|
45
|
+
const renamingRef = useRef(false);
|
|
36
46
|
|
|
37
47
|
useEffect(() => {
|
|
38
48
|
if (!props.selectedPath) return;
|
|
@@ -74,6 +84,34 @@ export function NotesWorkspace(props: NotesWorkspaceProps) {
|
|
|
74
84
|
return () => window.removeEventListener('keydown', handleModeShortcut);
|
|
75
85
|
}, [props.active, props.viewMode, props.authoringMode, props.onViewModeChange, props.onAuthoringModeChange]);
|
|
76
86
|
|
|
87
|
+
async function handleTitleBlur(event: React.FocusEvent<HTMLInputElement>): Promise<void> {
|
|
88
|
+
if (renamingRef.current || !props.selectedPath) return;
|
|
89
|
+
const newTitle = event.currentTarget.value.trim().replace(INVALID_TITLE_CHARS, '');
|
|
90
|
+
const currentTitle = titleFromPath(props.selectedPath);
|
|
91
|
+
if (!newTitle || newTitle === currentTitle) {
|
|
92
|
+
event.currentTarget.value = currentTitle;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const parts = props.selectedPath.split('/');
|
|
96
|
+
parts[parts.length - 1] = newTitle.endsWith('.md') ? newTitle : `${newTitle}.md`;
|
|
97
|
+
const newPath = parts.join('/');
|
|
98
|
+
if (newPath === props.selectedPath) {
|
|
99
|
+
event.currentTarget.value = currentTitle;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
renamingRef.current = true;
|
|
103
|
+
try {
|
|
104
|
+
await renameNotePath(props.selectedPath, newPath);
|
|
105
|
+
props.onSelectedPathChange(newPath);
|
|
106
|
+
publishInvalidation({ topics: ['notes'], reason: 'note:title-renamed', source: 'ui' });
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.warn('[notes-rename]', error);
|
|
109
|
+
event.currentTarget.value = currentTitle;
|
|
110
|
+
} finally {
|
|
111
|
+
renamingRef.current = false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
77
115
|
const showEditor = props.viewMode === 'raw' || props.viewMode === 'split';
|
|
78
116
|
const showPreview = props.viewMode === 'preview' || props.viewMode === 'split';
|
|
79
117
|
|
|
@@ -128,8 +166,17 @@ export function NotesWorkspace(props: NotesWorkspaceProps) {
|
|
|
128
166
|
{!props.selectedPath && props.viewMode !== 'settings' && <NotesEmptyState />}
|
|
129
167
|
{props.selectedPath && props.viewMode !== 'settings' && (
|
|
130
168
|
<div className="notes-document-grid">
|
|
169
|
+
<input
|
|
170
|
+
className="notes-inline-title"
|
|
171
|
+
key={props.selectedPath}
|
|
172
|
+
defaultValue={titleFromPath(props.selectedPath)}
|
|
173
|
+
onBlur={handleTitleBlur}
|
|
174
|
+
onKeyDown={event => { if (event.key === 'Enter') event.currentTarget.blur(); }}
|
|
175
|
+
spellCheck={false}
|
|
176
|
+
aria-label="Note title"
|
|
177
|
+
/>
|
|
131
178
|
{showEditor && <div className="notes-editor-pane">
|
|
132
|
-
<MarkdownEditor key={props.selectedPath} active={props.active && showEditor} authoringMode={props.authoringMode} content={document.content} wordWrap={props.wordWrap} onChange={document.setContent} />
|
|
179
|
+
<MarkdownEditor key={props.selectedPath} active={props.active && showEditor} authoringMode={props.authoringMode} content={document.content} notePath={props.selectedPath} wordWrap={props.wordWrap} onChange={document.setContent} />
|
|
133
180
|
</div>}
|
|
134
181
|
{showPreview && <MarkdownPreview markdown={document.content} />}
|
|
135
182
|
</div>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const ALLOWED_IMAGE_TYPES = new Set(['image/png', 'image/jpeg', 'image/webp', 'image/gif']);
|
|
2
|
+
|
|
3
|
+
const EXTENSION_BY_MIME: Record<string, string> = {
|
|
4
|
+
'image/png': 'png',
|
|
5
|
+
'image/jpeg': 'jpg',
|
|
6
|
+
'image/webp': 'webp',
|
|
7
|
+
'image/gif': 'gif',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const DATA_IMAGE_RE = /<img\b[^>]*\bsrc=["'](data:(image\/(?:png|jpeg|webp|gif));base64,([^"']+))["'][^>]*>/i;
|
|
11
|
+
const HTML_IMAGE_SRC_RE = /<img\b[^>]*\bsrc=["']([^"']+)["'][^>]*>/i;
|
|
12
|
+
|
|
13
|
+
function isAllowedImage(file: File | null): file is File {
|
|
14
|
+
return Boolean(file && ALLOWED_IMAGE_TYPES.has(file.type));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function fileFromDataUrl(mime: string, base64: string): File | null {
|
|
18
|
+
if (!ALLOWED_IMAGE_TYPES.has(mime)) return null;
|
|
19
|
+
try {
|
|
20
|
+
const binary = atob(base64.replace(/\s+/g, ''));
|
|
21
|
+
const bytes = new Uint8Array(binary.length);
|
|
22
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
23
|
+
bytes[index] = binary.charCodeAt(index);
|
|
24
|
+
}
|
|
25
|
+
const extension = EXTENSION_BY_MIME[mime] ?? 'png';
|
|
26
|
+
return new File([bytes], `pasted-image.${extension}`, { type: mime });
|
|
27
|
+
} catch {
|
|
28
|
+
console.warn('[notes-image-paste] Failed to decode clipboard image data URL');
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function namedClipboardFile(file: File): File {
|
|
34
|
+
if (file.name) return file;
|
|
35
|
+
const extension = EXTENSION_BY_MIME[file.type] ?? 'png';
|
|
36
|
+
return new File([file], `pasted-image.${extension}`, {
|
|
37
|
+
type: file.type,
|
|
38
|
+
lastModified: file.lastModified,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function firstClipboardImage(data: DataTransfer | null): File | null {
|
|
43
|
+
if (!data) return null;
|
|
44
|
+
for (const item of Array.from(data.items ?? [])) {
|
|
45
|
+
if (item.kind !== 'file' || !ALLOWED_IMAGE_TYPES.has(item.type)) continue;
|
|
46
|
+
const file = item.getAsFile();
|
|
47
|
+
if (isAllowedImage(file)) return namedClipboardFile(file);
|
|
48
|
+
}
|
|
49
|
+
for (const file of Array.from(data.files ?? [])) {
|
|
50
|
+
if (isAllowedImage(file)) return namedClipboardFile(file);
|
|
51
|
+
}
|
|
52
|
+
const html = data.getData?.('text/html') ?? '';
|
|
53
|
+
const match = DATA_IMAGE_RE.exec(html);
|
|
54
|
+
if (match) {
|
|
55
|
+
const [, , mime, base64] = match;
|
|
56
|
+
return fileFromDataUrl(mime, base64);
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const REMOTE_IMAGE_EXT_RE = /\.(?:png|jpe?g|gif|webp)$/i;
|
|
62
|
+
|
|
63
|
+
function cleanRemoteImageUrl(input: string): string | null {
|
|
64
|
+
const trimmed = input.trim();
|
|
65
|
+
if (!trimmed) return null;
|
|
66
|
+
try {
|
|
67
|
+
const url = new URL(trimmed);
|
|
68
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') return null;
|
|
69
|
+
url.hash = '';
|
|
70
|
+
return url.toString();
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function looksLikeImageUrl(url: string): boolean {
|
|
77
|
+
try { return REMOTE_IMAGE_EXT_RE.test(new URL(url).pathname); } catch { return false; }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function firstRemoteClipboardImageUrl(data: DataTransfer | null): string | null {
|
|
81
|
+
if (!data) return null;
|
|
82
|
+
const html = data.getData?.('text/html') ?? '';
|
|
83
|
+
const htmlMatch = HTML_IMAGE_SRC_RE.exec(html);
|
|
84
|
+
if (htmlMatch) {
|
|
85
|
+
const url = cleanRemoteImageUrl(htmlMatch[1]);
|
|
86
|
+
if (url && looksLikeImageUrl(url)) return url;
|
|
87
|
+
}
|
|
88
|
+
const uriList = data.getData?.('text/uri-list') ?? '';
|
|
89
|
+
for (const line of uriList.split(/\r?\n/)) {
|
|
90
|
+
if (!line || line.startsWith('#')) continue;
|
|
91
|
+
const url = cleanRemoteImageUrl(line);
|
|
92
|
+
if (url && looksLikeImageUrl(url)) return url;
|
|
93
|
+
}
|
|
94
|
+
const plain = cleanRemoteImageUrl(data.getData?.('text/plain') ?? '');
|
|
95
|
+
return plain && looksLikeImageUrl(plain) ? plain : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function hasImportableClipboardImage(data: DataTransfer | null): boolean {
|
|
99
|
+
return Boolean(firstClipboardImage(data) || firstRemoteClipboardImageUrl(data));
|
|
100
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { EditorView } from '@codemirror/view';
|
|
2
|
+
import { uploadNoteAsset, uploadRemoteNoteAsset } from '../../api';
|
|
3
|
+
import { firstClipboardImage, firstRemoteClipboardImageUrl, hasImportableClipboardImage } from './clipboard-images';
|
|
4
|
+
|
|
5
|
+
export type NotesImagePasteOptions = {
|
|
6
|
+
notePath: string;
|
|
7
|
+
onError?: (error: Error) => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function errorFromUnknown(error: unknown): Error {
|
|
11
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const NOTE_IMAGE_MAX_BYTES = 5 * 1024 * 1024;
|
|
15
|
+
|
|
16
|
+
async function compressImageFile(file: File, maxBytes: number): Promise<File> {
|
|
17
|
+
const bitmap = await createImageBitmap(file);
|
|
18
|
+
const maxDim = 4096;
|
|
19
|
+
let { width, height } = bitmap;
|
|
20
|
+
if (width > maxDim || height > maxDim) {
|
|
21
|
+
const scale = maxDim / Math.max(width, height);
|
|
22
|
+
width = Math.round(width * scale);
|
|
23
|
+
height = Math.round(height * scale);
|
|
24
|
+
}
|
|
25
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
26
|
+
const ctx = canvas.getContext('2d');
|
|
27
|
+
if (!ctx) throw new Error('Canvas 2D context unavailable');
|
|
28
|
+
ctx.drawImage(bitmap, 0, 0, width, height);
|
|
29
|
+
bitmap.close();
|
|
30
|
+
const name = file.name.replace(/\.\w+$/, '') + '.jpg';
|
|
31
|
+
for (const quality of [0.85, 0.7, 0.5, 0.3]) {
|
|
32
|
+
const blob = await canvas.convertToBlob({ type: 'image/jpeg', quality });
|
|
33
|
+
if (blob.size <= maxBytes) {
|
|
34
|
+
return new File([blob], name, { type: 'image/jpeg', lastModified: file.lastModified });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const half = new OffscreenCanvas(Math.round(width / 2), Math.round(height / 2));
|
|
38
|
+
const halfCtx = half.getContext('2d');
|
|
39
|
+
if (!halfCtx) throw new Error('Canvas 2D context unavailable');
|
|
40
|
+
halfCtx.drawImage(canvas, 0, 0, half.width, half.height);
|
|
41
|
+
const blob = await half.convertToBlob({ type: 'image/jpeg', quality: 0.7 });
|
|
42
|
+
if (blob.size > maxBytes) throw new Error('Image too large even after compression');
|
|
43
|
+
return new File([blob], name, { type: 'image/jpeg', lastModified: file.lastModified });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function uploadClipboardImageMarkdown(notePath: string, data: DataTransfer | null): Promise<string | null> {
|
|
47
|
+
let image = firstClipboardImage(data);
|
|
48
|
+
const remoteUrl = image ? null : firstRemoteClipboardImageUrl(data);
|
|
49
|
+
if (!image && !remoteUrl) return null;
|
|
50
|
+
if (image && image.size > NOTE_IMAGE_MAX_BYTES) {
|
|
51
|
+
image = await compressImageFile(image, NOTE_IMAGE_MAX_BYTES);
|
|
52
|
+
}
|
|
53
|
+
const result = image
|
|
54
|
+
? await uploadNoteAsset(notePath, image)
|
|
55
|
+
: await uploadRemoteNoteAsset(notePath, remoteUrl!);
|
|
56
|
+
return result.markdown;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function handleImageDataTransfer(
|
|
60
|
+
event: ClipboardEvent | DragEvent,
|
|
61
|
+
view: EditorView,
|
|
62
|
+
options: NotesImagePasteOptions,
|
|
63
|
+
): boolean {
|
|
64
|
+
const data = 'clipboardData' in event ? event.clipboardData : event.dataTransfer;
|
|
65
|
+
if (!hasImportableClipboardImage(data)) return false;
|
|
66
|
+
const textFallback = data?.getData('text/plain') ?? '';
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
void uploadClipboardImageMarkdown(options.notePath, data)
|
|
69
|
+
.then(result => {
|
|
70
|
+
if (result) view.dispatch(view.state.replaceSelection(result));
|
|
71
|
+
})
|
|
72
|
+
.catch(error => {
|
|
73
|
+
if (textFallback) view.dispatch(view.state.replaceSelection(textFallback));
|
|
74
|
+
options.onError?.(errorFromUnknown(error));
|
|
75
|
+
});
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function handleClipboardImagePaste(
|
|
80
|
+
event: ClipboardEvent,
|
|
81
|
+
view: EditorView,
|
|
82
|
+
options: NotesImagePasteOptions,
|
|
83
|
+
): boolean {
|
|
84
|
+
return handleImageDataTransfer(event, view, options);
|
|
85
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
DashboardNoteFileResponse,
|
|
3
|
+
DashboardNotesCapabilities,
|
|
4
|
+
DashboardNoteTreeEntry,
|
|
5
|
+
VaultIndexSnapshot,
|
|
6
|
+
} from '../types';
|
|
2
7
|
|
|
3
8
|
export type NotesViewMode = 'raw' | 'split' | 'preview' | 'settings';
|
|
4
9
|
export type NotesAuthoringMode = 'plain' | 'rich' | 'wysiwyg';
|
|
@@ -15,3 +20,5 @@ export type NoteConflictState = {
|
|
|
15
20
|
|
|
16
21
|
export type NotesTreeEntry = DashboardNoteTreeEntry;
|
|
17
22
|
export type NoteFile = DashboardNoteFileResponse;
|
|
23
|
+
export type NotesVaultIndexSnapshot = VaultIndexSnapshot;
|
|
24
|
+
export type NotesCapabilities = DashboardNotesCapabilities;
|
|
@@ -11,6 +11,7 @@ import { MermaidBlock } from './MermaidBlock';
|
|
|
11
11
|
import {
|
|
12
12
|
isSafeExternalHref,
|
|
13
13
|
markdownSanitizeSchema,
|
|
14
|
+
notesImageSrc,
|
|
14
15
|
safeMarkdownUrl,
|
|
15
16
|
} from './markdown-render-security';
|
|
16
17
|
|
|
@@ -70,6 +71,11 @@ export function MarkdownRenderer(props: MarkdownRendererProps) {
|
|
|
70
71
|
if (language === 'mermaid') return <MermaidBlock code={code} />;
|
|
71
72
|
return <CodeBlock code={code} language={language} />;
|
|
72
73
|
},
|
|
74
|
+
img: ({ src, alt, ...imageProps }: ComponentProps<'img'>) => {
|
|
75
|
+
const safeSrc = typeof src === 'string' ? notesImageSrc(src) : '';
|
|
76
|
+
if (!safeSrc) return null;
|
|
77
|
+
return <img {...imageProps} src={safeSrc} alt={alt ?? ''} loading="lazy" />;
|
|
78
|
+
},
|
|
73
79
|
}}
|
|
74
80
|
>
|
|
75
81
|
{props.markdown}
|
|
@@ -17,6 +17,24 @@ export function safeMarkdownUrl(url: string): string {
|
|
|
17
17
|
return isSafeExternalHref(url) ? url : '';
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export function notesImageSrc(src: string): string {
|
|
21
|
+
const trimmed = src.trim();
|
|
22
|
+
if (!trimmed || /^(?:javascript|data|file):/i.test(trimmed)) return '';
|
|
23
|
+
if (/^\/(?:Users|home|var|tmp|private|etc)\//i.test(trimmed)) return '';
|
|
24
|
+
const assetPath = trimmed.startsWith('./.assets/')
|
|
25
|
+
? trimmed.slice(2)
|
|
26
|
+
: trimmed.startsWith('.assets/')
|
|
27
|
+
? trimmed
|
|
28
|
+
: null;
|
|
29
|
+
if (assetPath) {
|
|
30
|
+
if (assetPath.includes('\\') || assetPath.split('/').some(segment => !segment || segment === '.' || segment === '..')) {
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
return `/api/dashboard/notes/asset?path=${encodeURIComponent(assetPath)}`;
|
|
34
|
+
}
|
|
35
|
+
return isSafeExternalHref(trimmed) ? trimmed : '';
|
|
36
|
+
}
|
|
37
|
+
|
|
20
38
|
// rehype-raw is intentionally absent: Notes render user-authored markdown only,
|
|
21
39
|
// and raw HTML must stay disabled before future WYSIWYG reuse.
|
|
22
40
|
export const markdownSanitizeSchema: RehypeSanitizeSchema = {
|
|
@@ -24,7 +42,7 @@ export const markdownSanitizeSchema: RehypeSanitizeSchema = {
|
|
|
24
42
|
attributes: {
|
|
25
43
|
...defaultSchema.attributes,
|
|
26
44
|
code: [
|
|
27
|
-
...(defaultSchema.attributes?.code ?? []),
|
|
45
|
+
...(defaultSchema.attributes?.['code'] ?? []),
|
|
28
46
|
['className', /^language-./, 'math-inline', 'math-display'],
|
|
29
47
|
],
|
|
30
48
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EditorView } from '@codemirror/view';
|
|
2
|
+
import { handleClipboardImagePaste, handleImageDataTransfer, type NotesImagePasteOptions } from '../image-assets/insert-image-markdown';
|
|
2
3
|
|
|
3
4
|
function htmlToPlainText(html: string): string {
|
|
4
5
|
const template = document.createElement('template');
|
|
@@ -6,9 +7,10 @@ function htmlToPlainText(html: string): string {
|
|
|
6
7
|
return template.content.textContent || '';
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
export function richMarkdownPastePolicy() {
|
|
10
|
+
export function richMarkdownPastePolicy(options?: NotesImagePasteOptions) {
|
|
10
11
|
return EditorView.domEventHandlers({
|
|
11
12
|
paste(event, view) {
|
|
13
|
+
if (options && handleClipboardImagePaste(event, view, options)) return true;
|
|
12
14
|
const text = event.clipboardData?.getData('text/plain');
|
|
13
15
|
if (text) return false;
|
|
14
16
|
const html = event.clipboardData?.getData('text/html');
|
|
@@ -17,6 +19,8 @@ export function richMarkdownPastePolicy() {
|
|
|
17
19
|
view.dispatch(view.state.replaceSelection(htmlToPlainText(html)));
|
|
18
20
|
return true;
|
|
19
21
|
},
|
|
22
|
+
drop(event, view) {
|
|
23
|
+
return Boolean(options && handleImageDataTransfer(event, view, options));
|
|
24
|
+
},
|
|
20
25
|
});
|
|
21
26
|
}
|
|
22
|
-
|