painapple-code 1.0.0__py3-none-any.whl
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.
- painapple_code/__init__.py +30 -0
- painapple_code/__main__.py +18 -0
- painapple_code/_version.py +24 -0
- painapple_code/auth_middleware.py +930 -0
- painapple_code/cli/__init__.py +204 -0
- painapple_code/cli/deploy/__init__.py +15 -0
- painapple_code/cli/deploy/claude_seed.py +41 -0
- painapple_code/cli/deploy/config.py +260 -0
- painapple_code/cli/deploy/container.py +715 -0
- painapple_code/cli/deploy/launch.py +163 -0
- painapple_code/cli/deploy/runtime.py +223 -0
- painapple_code/cli/lifecycle_cmd.py +443 -0
- painapple_code/cli/list_cmd.py +572 -0
- painapple_code/cli/manage_cmd.py +670 -0
- painapple_code/cli/netinfo.py +125 -0
- painapple_code/cli/profiles.py +390 -0
- painapple_code/cli/serve_args.py +163 -0
- painapple_code/cli/serve_config.py +256 -0
- painapple_code/cli/setup_cmd.py +664 -0
- painapple_code/cli/ui.py +438 -0
- painapple_code/cost_analytics.py +879 -0
- painapple_code/data/models.defaults.yaml +18 -0
- painapple_code/data/models.yaml +18 -0
- painapple_code/data/presets.defaults.json +32 -0
- painapple_code/data/strings.yaml +2269 -0
- painapple_code/helpers.py +406 -0
- painapple_code/paths.py +1514 -0
- painapple_code/prompt_explorer.py +984 -0
- painapple_code/providers/__init__.py +224 -0
- painapple_code/providers/base.py +710 -0
- painapple_code/providers/claude/__init__.py +74 -0
- painapple_code/providers/claude/capabilities.py +218 -0
- painapple_code/providers/claude/errors.py +79 -0
- painapple_code/providers/claude/launch.py +50 -0
- painapple_code/providers/claude/summary.py +89 -0
- painapple_code/providers/claude/translate.py +74 -0
- painapple_code/providers/claude_sdk/__init__.py +139 -0
- painapple_code/providers/claude_sdk/driver.py +412 -0
- painapple_code/providers/codex/__init__.py +87 -0
- painapple_code/providers/codex/capabilities.py +321 -0
- painapple_code/providers/codex/errors.py +70 -0
- painapple_code/providers/codex/launch.py +142 -0
- painapple_code/providers/codex/summary.py +196 -0
- painapple_code/providers/codex/translate.py +204 -0
- painapple_code/providers/codex_app_server/__init__.py +100 -0
- painapple_code/providers/codex_app_server/capabilities.py +41 -0
- painapple_code/providers/codex_app_server/launch.py +202 -0
- painapple_code/providers/codex_app_server/summary.py +291 -0
- painapple_code/providers/codex_app_server/translate.py +263 -0
- painapple_code/providers/codex_app_server/transport.py +238 -0
- painapple_code/routes/__init__.py +0 -0
- painapple_code/routes/api_agents.py +441 -0
- painapple_code/routes/api_app.py +368 -0
- painapple_code/routes/api_app_commit_sections.py +127 -0
- painapple_code/routes/api_app_config.py +997 -0
- painapple_code/routes/api_app_session_prefs.py +217 -0
- painapple_code/routes/api_browser.py +690 -0
- painapple_code/routes/api_commands.py +374 -0
- painapple_code/routes/api_costs.py +128 -0
- painapple_code/routes/api_drafts.py +139 -0
- painapple_code/routes/api_exec.py +149 -0
- painapple_code/routes/api_files.py +542 -0
- painapple_code/routes/api_git.py +657 -0
- painapple_code/routes/api_logs.py +649 -0
- painapple_code/routes/api_plugins.py +275 -0
- painapple_code/routes/api_project_config.py +418 -0
- painapple_code/routes/api_prompts.py +231 -0
- painapple_code/routes/api_providers.py +53 -0
- painapple_code/routes/api_search.py +411 -0
- painapple_code/routes/api_session_stash.py +234 -0
- painapple_code/routes/api_session_welcome.py +151 -0
- painapple_code/routes/api_sessions.py +742 -0
- painapple_code/routes/api_shadow.py +323 -0
- painapple_code/routes/api_shadow_db.py +512 -0
- painapple_code/routes/api_shadow_files.py +332 -0
- painapple_code/routes/api_shadow_search.py +476 -0
- painapple_code/routes/api_skills.py +610 -0
- painapple_code/routes/api_tasks.py +147 -0
- painapple_code/routes/api_terminal.py +540 -0
- painapple_code/routes/api_upload.py +293 -0
- painapple_code/routes/api_viewer.py +229 -0
- painapple_code/routes/dependencies.py +109 -0
- painapple_code/routes/ws_chat.py +837 -0
- painapple_code/server.py +2208 -0
- painapple_code/server_logging.py +263 -0
- painapple_code/services/__init__.py +0 -0
- painapple_code/services/agent_session.py +2738 -0
- painapple_code/session_store.py +253 -0
- painapple_code/session_store_core.py +879 -0
- painapple_code/shadow_db.py +830 -0
- painapple_code/shadow_db_plans.py +369 -0
- painapple_code/shadow_db_queries.py +595 -0
- painapple_code/shadow_db_schema.py +676 -0
- painapple_code/shadow_git.py +1296 -0
- painapple_code/shadow_git_frontmatter.py +314 -0
- painapple_code/shadow_git_sections.py +520 -0
- painapple_code/shadow_git_summary.py +462 -0
- painapple_code/shadow_parser.py +560 -0
- painapple_code/static/css/00-variables.css +276 -0
- painapple_code/static/css/01-base.css +245 -0
- painapple_code/static/css/02-tooltips.css +36 -0
- painapple_code/static/css/09-left-rail.css +448 -0
- painapple_code/static/css/10-header.css +777 -0
- painapple_code/static/css/11-connection-bar.css +120 -0
- painapple_code/static/css/20-messages.css +750 -0
- painapple_code/static/css/21-tool-blocks.css +2340 -0
- painapple_code/static/css/22-edit-diff.css +619 -0
- painapple_code/static/css/23-thinking.css +1584 -0
- painapple_code/static/css/24-background-tasks.css +359 -0
- painapple_code/static/css/25-context-block.css +252 -0
- painapple_code/static/css/26-session-setup.css +144 -0
- painapple_code/static/css/30-markdown.css +854 -0
- painapple_code/static/css/40-input.css +2418 -0
- painapple_code/static/css/41-autocomplete.css +127 -0
- painapple_code/static/css/42-activity-strip.css +97 -0
- painapple_code/static/css/43-file-autocomplete.css +121 -0
- painapple_code/static/css/44-snippets-autocomplete.css +224 -0
- painapple_code/static/css/45-selection-bar.css +433 -0
- painapple_code/static/css/46-keyboard-bar.css +119 -0
- painapple_code/static/css/47-keyboard-longpress.css +116 -0
- painapple_code/static/css/47-token-profile.css +106 -0
- painapple_code/static/css/50-welcome.css +3095 -0
- painapple_code/static/css/51-modals.css +301 -0
- painapple_code/static/css/52-utilities.css +77 -0
- painapple_code/static/css/53-image-upload.css +456 -0
- painapple_code/static/css/54-todo-list.css +102 -0
- painapple_code/static/css/55-file-upload.css +113 -0
- painapple_code/static/css/56-chat-navigator.css +218 -0
- painapple_code/static/css/56-permission-card.css +332 -0
- painapple_code/static/css/56-plan-approval.css +204 -0
- painapple_code/static/css/56-question-form.css +602 -0
- painapple_code/static/css/57-swipe-indicator.css +95 -0
- painapple_code/static/css/58-session-families.css +627 -0
- painapple_code/static/css/59-about.css +196 -0
- painapple_code/static/css/61-log-explorer.css +723 -0
- painapple_code/static/css/62-json-tree.css +489 -0
- painapple_code/static/css/62-skills-widget.css +650 -0
- painapple_code/static/css/63-commands-widget.css +30 -0
- painapple_code/static/css/63-plugins-widget.css +174 -0
- painapple_code/static/css/63-terminal.css +516 -0
- painapple_code/static/css/64-search-files-widget.css +448 -0
- painapple_code/static/css/65-snippets-widget.css +426 -0
- painapple_code/static/css/66-active-sessions.css +979 -0
- painapple_code/static/css/66-agents-widget.css +265 -0
- painapple_code/static/css/66-file-explorer-widget.css +855 -0
- painapple_code/static/css/66-git-panel.css +791 -0
- painapple_code/static/css/66-history-explorer.css +1496 -0
- painapple_code/static/css/67-diff-viewer.css +602 -0
- painapple_code/static/css/67-editor-view.css +748 -0
- painapple_code/static/css/67-file-preview-widget.css +1873 -0
- painapple_code/static/css/68-browser-widget.css +337 -0
- painapple_code/static/css/68-compare-wizard.css +355 -0
- painapple_code/static/css/68-csv-preview.css +106 -0
- painapple_code/static/css/68-diff-preview.css +103 -0
- painapple_code/static/css/68-helpers-install-widget.css +491 -0
- painapple_code/static/css/68-image-annotate.css +346 -0
- painapple_code/static/css/68-terminal-view.css +98 -0
- painapple_code/static/css/68-uploads-widget.css +229 -0
- painapple_code/static/css/69-cost-analytics.css +537 -0
- painapple_code/static/css/70-config-panel.css +3736 -0
- painapple_code/static/css/70-excalidraw.css +136 -0
- painapple_code/static/css/70-prompt-explorer.css +890 -0
- painapple_code/static/css/71-chart.css +111 -0
- painapple_code/static/css/72-discussion-sidebar.css +678 -0
- painapple_code/static/css/74-debug-logs.css +656 -0
- painapple_code/static/css/80-open-dialog.css +146 -0
- painapple_code/static/css/80-quick-switcher.css +383 -0
- painapple_code/static/css/80-widget-system.css +1763 -0
- painapple_code/static/css/81-quick-actions.css +1020 -0
- painapple_code/static/css/82-context-popover.css +1233 -0
- painapple_code/static/css/83-context-menu.css +339 -0
- painapple_code/static/css/85-lazy-loading.css +102 -0
- painapple_code/static/css/86-zen-mode.css +708 -0
- painapple_code/static/css/87-grid-switcher.css +346 -0
- painapple_code/static/css/login.css +198 -0
- painapple_code/static/dist/app.bundle.js +7177 -0
- painapple_code/static/feature-triage.html +446 -0
- painapple_code/static/icons/apple-touch-icon.png +0 -0
- painapple_code/static/icons/favicon-32.png +0 -0
- painapple_code/static/icons/favicon.ico +0 -0
- painapple_code/static/icons/icon-128.png +0 -0
- painapple_code/static/icons/icon-144.png +0 -0
- painapple_code/static/icons/icon-152.png +0 -0
- painapple_code/static/icons/icon-180.png +0 -0
- painapple_code/static/icons/icon-192.png +0 -0
- painapple_code/static/icons/icon-384.png +0 -0
- painapple_code/static/icons/icon-512.png +0 -0
- painapple_code/static/icons/icon-72.png +0 -0
- painapple_code/static/icons/icon-96.png +0 -0
- painapple_code/static/js/action-delegate.js +187 -0
- painapple_code/static/js/activity-strip.js +193 -0
- painapple_code/static/js/app/context-menus.js +619 -0
- painapple_code/static/js/app/file-preview.js +163 -0
- painapple_code/static/js/app/init-ui.js +574 -0
- painapple_code/static/js/app/input-events.js +561 -0
- painapple_code/static/js/app/input-resize.js +313 -0
- painapple_code/static/js/app/message-actions.js +419 -0
- painapple_code/static/js/app/panels.js +210 -0
- painapple_code/static/js/app/rendering-delegators.js +246 -0
- painapple_code/static/js/app/server-loading.js +365 -0
- painapple_code/static/js/app/session-ops.js +287 -0
- painapple_code/static/js/app/session-switch.js +443 -0
- painapple_code/static/js/app/settings-helpers.js +280 -0
- painapple_code/static/js/app/startup-welcome.js +290 -0
- painapple_code/static/js/app-context.js +157 -0
- painapple_code/static/js/app.js +2134 -0
- painapple_code/static/js/auth-fetch.js +46 -0
- painapple_code/static/js/background-tasks.js +274 -0
- painapple_code/static/js/boot.js +73 -0
- painapple_code/static/js/caret-position.js +171 -0
- painapple_code/static/js/chat-navigator.js +710 -0
- painapple_code/static/js/chat-search.js +323 -0
- painapple_code/static/js/command-executor.js +176 -0
- painapple_code/static/js/command-store.js +497 -0
- painapple_code/static/js/components.js +1414 -0
- painapple_code/static/js/config.js +218 -0
- painapple_code/static/js/context-menu.js +740 -0
- painapple_code/static/js/controllers/chat-controller.js +4252 -0
- painapple_code/static/js/controllers/dialog-controller.js +217 -0
- painapple_code/static/js/controllers/tab-controller.js +1828 -0
- painapple_code/static/js/controllers/thinking-controller.js +915 -0
- painapple_code/static/js/diff-utils.js +486 -0
- painapple_code/static/js/drafts-pill.js +95 -0
- painapple_code/static/js/editor-view.js +815 -0
- painapple_code/static/js/effort-settings.js +430 -0
- painapple_code/static/js/feature-triage.js +811 -0
- painapple_code/static/js/file-autocomplete.js +516 -0
- painapple_code/static/js/file-tabs.js +123 -0
- painapple_code/static/js/gestures.js +448 -0
- painapple_code/static/js/grid-switcher.js +334 -0
- painapple_code/static/js/input-handler.js +1472 -0
- painapple_code/static/js/keyboard-bar.js +495 -0
- painapple_code/static/js/layout-switcher.js +109 -0
- painapple_code/static/js/linkify-utils.js +200 -0
- painapple_code/static/js/login.js +141 -0
- painapple_code/static/js/open-dialog.js +879 -0
- painapple_code/static/js/orphan-terminals.js +373 -0
- painapple_code/static/js/path-utils.js +179 -0
- painapple_code/static/js/perf-marks.js +102 -0
- painapple_code/static/js/permission-settings.js +475 -0
- painapple_code/static/js/pointer-input.js +59 -0
- painapple_code/static/js/preview/json-tree-menu.js +226 -0
- painapple_code/static/js/preview/json-tree-toolbar.js +291 -0
- painapple_code/static/js/preview/json-tree.js +457 -0
- painapple_code/static/js/preview/preview-breadcrumb.js +387 -0
- painapple_code/static/js/preview/preview-edit.js +367 -0
- painapple_code/static/js/preview/preview-events.js +361 -0
- painapple_code/static/js/preview/preview-history.js +567 -0
- painapple_code/static/js/preview/preview-inline-edit.js +869 -0
- painapple_code/static/js/preview/preview-poll.js +108 -0
- painapple_code/static/js/preview/preview-render.js +572 -0
- painapple_code/static/js/preview/preview-search.js +421 -0
- painapple_code/static/js/preview/preview-state.js +256 -0
- painapple_code/static/js/preview/preview-utils.js +251 -0
- painapple_code/static/js/preview-plugins/chart-plugin.js +22 -0
- painapple_code/static/js/preview-plugins/csv-plugin.js +188 -0
- painapple_code/static/js/preview-plugins/diff-plugin.js +258 -0
- painapple_code/static/js/preview-plugins/excalidraw-plugin.js +25 -0
- painapple_code/static/js/preview-plugins/html-plugin.js +104 -0
- painapple_code/static/js/preview-plugins/image-plugin.js +20 -0
- painapple_code/static/js/preview-plugins/index.js +34 -0
- painapple_code/static/js/preview-plugins/json-plugin.js +99 -0
- painapple_code/static/js/preview-plugins/jsonl-plugin.js +152 -0
- painapple_code/static/js/preview-plugins/markdown-plugin.js +266 -0
- painapple_code/static/js/preview-plugins/panzoom-plugin.js +63 -0
- painapple_code/static/js/preview-plugins/plugin-helpers.js +282 -0
- painapple_code/static/js/project-color-picker.js +110 -0
- painapple_code/static/js/project-colors.js +163 -0
- painapple_code/static/js/prompt-favorites.js +187 -0
- painapple_code/static/js/quick-actions-menu.js +1736 -0
- painapple_code/static/js/quick-actions-registry.js +1239 -0
- painapple_code/static/js/quick-switcher/controller.js +294 -0
- painapple_code/static/js/quick-switcher/fuzzy-scorer.js +123 -0
- painapple_code/static/js/quick-switcher/index.js +30 -0
- painapple_code/static/js/quick-switcher/providers/base-provider.js +51 -0
- painapple_code/static/js/quick-switcher/providers/command-provider.js +56 -0
- painapple_code/static/js/quick-switcher/providers/file-provider.js +291 -0
- painapple_code/static/js/quick-switcher/providers/panel-provider.js +132 -0
- painapple_code/static/js/quick-switcher/providers/project-provider.js +361 -0
- painapple_code/static/js/quick-switcher/providers/read-files-provider.js +104 -0
- painapple_code/static/js/quick-switcher/providers/skills-provider.js +129 -0
- painapple_code/static/js/quick-switcher/registry.js +69 -0
- painapple_code/static/js/quick-switcher/ui/item.js +54 -0
- painapple_code/static/js/quick-switcher/ui/picker.js +464 -0
- painapple_code/static/js/recent-opens.js +40 -0
- painapple_code/static/js/scroll-manager.js +530 -0
- painapple_code/static/js/scroll-state-machine.js +359 -0
- painapple_code/static/js/selection/action-bar.js +793 -0
- painapple_code/static/js/selection/index.js +15 -0
- painapple_code/static/js/selection/selection-handler.js +877 -0
- painapple_code/static/js/selection/state.js +181 -0
- painapple_code/static/js/session/agent-progress.js +202 -0
- painapple_code/static/js/session/handle-agent-message.js +481 -0
- painapple_code/static/js/session/handle-message.js +550 -0
- painapple_code/static/js/session/interactive.js +587 -0
- painapple_code/static/js/session/message-store.js +271 -0
- painapple_code/static/js/session/messages.js +253 -0
- painapple_code/static/js/session/persistence.js +179 -0
- painapple_code/static/js/session/restore.js +136 -0
- painapple_code/static/js/session/sync.js +333 -0
- painapple_code/static/js/session-container-pool.js +371 -0
- painapple_code/static/js/session-setup-panel.js +274 -0
- painapple_code/static/js/session.js +1047 -0
- painapple_code/static/js/shortcut-hints.js +272 -0
- painapple_code/static/js/shortcuts.js +1146 -0
- painapple_code/static/js/skills-autocomplete.js +262 -0
- painapple_code/static/js/snippets-autocomplete.js +738 -0
- painapple_code/static/js/stash-refs-view.js +83 -0
- painapple_code/static/js/stash-ui.js +768 -0
- painapple_code/static/js/stash.js +785 -0
- painapple_code/static/js/status-bar.js +1184 -0
- painapple_code/static/js/token-profile.js +274 -0
- painapple_code/static/js/tool-renderer-blocks.js +1626 -0
- painapple_code/static/js/tool-renderer-thinking.js +813 -0
- painapple_code/static/js/tool-renderer.js +1500 -0
- painapple_code/static/js/tooltips.js +197 -0
- painapple_code/static/js/upload-manager.js +1019 -0
- painapple_code/static/js/utils.js +521 -0
- painapple_code/static/js/welcome/api.js +363 -0
- painapple_code/static/js/welcome/cards.js +626 -0
- painapple_code/static/js/welcome/context-menu.js +585 -0
- painapple_code/static/js/welcome/families.js +801 -0
- painapple_code/static/js/welcome/preview.js +234 -0
- painapple_code/static/js/welcome/state.js +129 -0
- painapple_code/static/js/welcome.js +1874 -0
- painapple_code/static/js/widget-system/base-widget.js +717 -0
- painapple_code/static/js/widget-system/device-manager.js +178 -0
- painapple_code/static/js/widget-system/event-bus.js +152 -0
- painapple_code/static/js/widget-system/icons.js +120 -0
- painapple_code/static/js/widget-system/index.js +49 -0
- painapple_code/static/js/widget-system/init.js +54 -0
- painapple_code/static/js/widget-system/types/bottom-sheet.js +307 -0
- painapple_code/static/js/widget-system/types/floating.js +915 -0
- painapple_code/static/js/widget-system/types/index.js +40 -0
- painapple_code/static/js/widget-system/types/modal.js +147 -0
- painapple_code/static/js/widget-system/types/sidebar.js +158 -0
- painapple_code/static/js/widget-system/types/tab.js +146 -0
- painapple_code/static/js/widget-system/types/top-sheet.js +203 -0
- painapple_code/static/js/widget-system/widget-manager.js +1187 -0
- painapple_code/static/js/widgets/about-widget.js +260 -0
- painapple_code/static/js/widgets/active-sessions-widget.js +760 -0
- painapple_code/static/js/widgets/agents-widget.js +897 -0
- painapple_code/static/js/widgets/browser-widget.js +579 -0
- painapple_code/static/js/widgets/commands-widget.js +694 -0
- painapple_code/static/js/widgets/config/commit-sections.js +449 -0
- painapple_code/static/js/widgets/config/dir-autocomplete.js +224 -0
- painapple_code/static/js/widgets/config/gestures.js +161 -0
- painapple_code/static/js/widgets/config/models-tab.js +988 -0
- painapple_code/static/js/widgets/config/quick-actions-tab.js +882 -0
- painapple_code/static/js/widgets/config/shortcut-editor.js +587 -0
- painapple_code/static/js/widgets/config/state.js +517 -0
- painapple_code/static/js/widgets/config/system-controls.js +90 -0
- painapple_code/static/js/widgets/config-widget.js +1535 -0
- painapple_code/static/js/widgets/cost-analytics-widget.js +621 -0
- painapple_code/static/js/widgets/debug-widget.js +693 -0
- painapple_code/static/js/widgets/diff-viewer-widget.js +1420 -0
- painapple_code/static/js/widgets/discussion-widget.js +1107 -0
- painapple_code/static/js/widgets/file-explorer-widget.js +2166 -0
- painapple_code/static/js/widgets/file-preview-widget.js +861 -0
- painapple_code/static/js/widgets/git-widget.js +794 -0
- painapple_code/static/js/widgets/helpers-install-widget.js +720 -0
- painapple_code/static/js/widgets/history-explorer-widget.js +1804 -0
- painapple_code/static/js/widgets/image-annotate-widget.js +951 -0
- painapple_code/static/js/widgets/image-preview-widget.js +692 -0
- painapple_code/static/js/widgets/index.js +176 -0
- painapple_code/static/js/widgets/log-explorer-widget.js +1057 -0
- painapple_code/static/js/widgets/plugins-widget.js +305 -0
- painapple_code/static/js/widgets/prompt-explorer-widget.js +1119 -0
- painapple_code/static/js/widgets/search-files-widget.js +529 -0
- painapple_code/static/js/widgets/skills-widget.js +843 -0
- painapple_code/static/js/widgets/snippets-widget.js +479 -0
- painapple_code/static/js/widgets/sub-agents-widget.js +333 -0
- painapple_code/static/js/widgets/tasks-widget.js +379 -0
- painapple_code/static/js/widgets/terminal/connection.js +283 -0
- painapple_code/static/js/widgets/terminal/gestures.js +357 -0
- painapple_code/static/js/widgets/terminal/init.js +642 -0
- painapple_code/static/js/widgets/terminal/link-providers.js +532 -0
- painapple_code/static/js/widgets/terminal/osc52.js +213 -0
- painapple_code/static/js/widgets/terminal/render.js +159 -0
- painapple_code/static/js/widgets/terminal/size.js +54 -0
- painapple_code/static/js/widgets/terminal/state.js +211 -0
- painapple_code/static/js/widgets/terminal/wrap-utils.js +101 -0
- painapple_code/static/js/widgets/terminal-widget.js +793 -0
- painapple_code/static/js/widgets/uploads-widget.js +461 -0
- painapple_code/static/js/widgets/zen-widget.js +1001 -0
- painapple_code/static/login.html +51 -0
- painapple_code/static/manifest.json +73 -0
- painapple_code/static/sw.js +293 -0
- painapple_code/static/vendor/LICENSES.txt +769 -0
- painapple_code/static/vendor/README.md +103 -0
- painapple_code/static/vendor/codemirror.js +35 -0
- painapple_code/static/vendor/codemirror.js.LEGAL.txt +304 -0
- painapple_code/static/vendor/github-markdown-dark.min.css +1124 -0
- painapple_code/static/vendor/highlight-github-dark.min.css +10 -0
- painapple_code/static/vendor/highlight-lang-dockerfile.min.js +8 -0
- painapple_code/static/vendor/highlight-lang-nginx.min.js +21 -0
- painapple_code/static/vendor/highlight-lang-properties.min.js +10 -0
- painapple_code/static/vendor/highlight-lang-scala.min.js +28 -0
- painapple_code/static/vendor/highlight.min.js +1213 -0
- painapple_code/static/vendor/marked.min.js +6 -0
- painapple_code/static/vendor/purify.min.js +3 -0
- painapple_code/static/vendor/xterm-addon-fit.js +2 -0
- painapple_code/static/vendor/xterm.css +209 -0
- painapple_code/static/vendor/xterm.js +2 -0
- painapple_code/static/web-client.html +635 -0
- painapple_code/subprocess_registry.py +535 -0
- painapple_code/tls_cert.py +79 -0
- painapple_code/tools/agents/shadow-git-helper.md +400 -0
- painapple_code/tools/excalidraw-to-svg.js +143 -0
- painapple_code/tools/install-helpers.sh +148 -0
- painapple_code/tools/shadow-git +320 -0
- painapple_code/tools/shadow-query +120 -0
- painapple_code/tools/vegalite-to-svg.js +79 -0
- painapple_code/turn_query.py +766 -0
- painapple_code/turn_tracker.py +221 -0
- painapple_code/utils/__init__.py +0 -0
- painapple_code/utils/agent_cli.py +306 -0
- painapple_code/utils/chart.py +103 -0
- painapple_code/utils/excalidraw.py +107 -0
- painapple_code/utils/file_lock.py +162 -0
- painapple_code/utils/file_paths.py +648 -0
- painapple_code/utils/generate_icons.py +251 -0
- painapple_code/utils/proc.py +205 -0
- painapple_code/utils/pty_backend.py +59 -0
- painapple_code/utils/pty_posix.py +160 -0
- painapple_code/utils/pty_windows.py +245 -0
- painapple_code/utils/token_profiles.py +91 -0
- painapple_code/viewer_templates.py +657 -0
- painapple_code/welcome_search.py +879 -0
- painapple_code-1.0.0.dist-info/METADATA +292 -0
- painapple_code-1.0.0.dist-info/RECORD +436 -0
- painapple_code-1.0.0.dist-info/WHEEL +5 -0
- painapple_code-1.0.0.dist-info/entry_points.txt +2 -0
- painapple_code-1.0.0.dist-info/licenses/LICENSE +661 -0
- painapple_code-1.0.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +261 -0
- painapple_code-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSONL (JSON Lines) preview plugin
|
|
3
|
+
*
|
|
4
|
+
* Handles: .jsonl
|
|
5
|
+
* Renders one collapsible tree per line, with line numbers, search,
|
|
6
|
+
* and expand/collapse-all controls. Falls through to code/edit modes.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { escapeHtml } from './plugin-helpers.js';
|
|
10
|
+
import { renderJsonTree, bindJsonTreeEvents, valueContainsMatch } from '../preview/json-tree.js';
|
|
11
|
+
import {
|
|
12
|
+
initJsonToolbarState, normalizeToolbarState, treeOptions,
|
|
13
|
+
renderJsonToolbar, bindJsonToolbar,
|
|
14
|
+
} from '../preview/json-tree-toolbar.js';
|
|
15
|
+
import { bindJsonTreeContextMenu } from '../preview/json-tree-menu.js';
|
|
16
|
+
import S from '../strings.js';
|
|
17
|
+
|
|
18
|
+
const treeIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h4v4H3zM3 14h4v4H3zM11 6h10M11 10h7M11 14h10M11 18h7"/></svg>`;
|
|
19
|
+
|
|
20
|
+
const MAX_LINES = 5000;
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
id: 'jsonl',
|
|
24
|
+
|
|
25
|
+
match(path) {
|
|
26
|
+
if (!path) return false;
|
|
27
|
+
return path.toLowerCase().endsWith('.jsonl');
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
needsFetch: true,
|
|
31
|
+
editable: true,
|
|
32
|
+
|
|
33
|
+
viewModes: [{
|
|
34
|
+
mode: 'tree',
|
|
35
|
+
label: S.preview?.json_tree_label || 'Tree',
|
|
36
|
+
icon: treeIcon,
|
|
37
|
+
}],
|
|
38
|
+
|
|
39
|
+
defaultViewMode: 'tree',
|
|
40
|
+
|
|
41
|
+
initState() {
|
|
42
|
+
return initJsonToolbarState();
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
renderBody(state, helpers) {
|
|
46
|
+
if (state.viewMode !== 'tree') return null;
|
|
47
|
+
|
|
48
|
+
const ps = normalizeToolbarState(state.pluginState);
|
|
49
|
+
const search = (ps.search || '').trim();
|
|
50
|
+
const searchLower = search.toLowerCase();
|
|
51
|
+
|
|
52
|
+
const allLines = (state.content || '').split('\n');
|
|
53
|
+
const truncated = allLines.length > MAX_LINES;
|
|
54
|
+
const lines = truncated ? allLines.slice(0, MAX_LINES) : allLines;
|
|
55
|
+
|
|
56
|
+
const parsed = [];
|
|
57
|
+
lines.forEach((raw, idx) => {
|
|
58
|
+
if (!raw.trim()) return;
|
|
59
|
+
try {
|
|
60
|
+
parsed.push({ lineNum: idx + 1, value: JSON.parse(raw), raw });
|
|
61
|
+
} catch (e) {
|
|
62
|
+
parsed.push({ lineNum: idx + 1, error: e.message, raw });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const visible = search
|
|
67
|
+
? parsed.filter(p => p.error
|
|
68
|
+
? p.raw.toLowerCase().includes(searchLower)
|
|
69
|
+
: valueContainsMatch(p.value, searchLower))
|
|
70
|
+
: parsed;
|
|
71
|
+
|
|
72
|
+
const rows = visible.map(p => {
|
|
73
|
+
if (p.error) {
|
|
74
|
+
return `
|
|
75
|
+
<div class="jsonl-row jsonl-row-error">
|
|
76
|
+
<div class="jsonl-row-num">${p.lineNum}</div>
|
|
77
|
+
<div class="jsonl-row-content">
|
|
78
|
+
<div class="json-parse-error"><strong>Invalid JSON:</strong> ${escapeHtml(p.error)}</div>
|
|
79
|
+
<pre class="json-raw">${escapeHtml(p.raw)}</pre>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
return `
|
|
85
|
+
<div class="jsonl-row">
|
|
86
|
+
<div class="jsonl-row-num">${p.lineNum}</div>
|
|
87
|
+
<div class="jsonl-row-content">${renderJsonTree(p.value, treeOptions(ps, {
|
|
88
|
+
collapseRoot: true,
|
|
89
|
+
// Namespace expand-state paths per source line — line
|
|
90
|
+
// numbers are stable across a re-filter, indexes aren't.
|
|
91
|
+
path: `L${p.lineNum}`,
|
|
92
|
+
}))}</div>
|
|
93
|
+
</div>
|
|
94
|
+
`;
|
|
95
|
+
}).join('');
|
|
96
|
+
|
|
97
|
+
const countLabel = search
|
|
98
|
+
? `${visible.length} / ${parsed.length} lines`
|
|
99
|
+
: `${parsed.length} line${parsed.length !== 1 ? 's' : ''}`;
|
|
100
|
+
const truncNote = truncated
|
|
101
|
+
? `<span class="jsonl-trunc-note">first ${MAX_LINES} lines shown (file has ${allLines.length})</span>`
|
|
102
|
+
: '';
|
|
103
|
+
|
|
104
|
+
const body = parsed.length === 0
|
|
105
|
+
? `<div class="jsonl-empty">No JSON lines found</div>`
|
|
106
|
+
: visible.length === 0
|
|
107
|
+
? `<div class="jsonl-empty">No matches</div>`
|
|
108
|
+
: `<div class="jsonl-rows">${rows}</div>`;
|
|
109
|
+
|
|
110
|
+
return `
|
|
111
|
+
<div class="preview-body jsonl-preview-body">
|
|
112
|
+
${renderJsonToolbar(ps)}
|
|
113
|
+
<div class="jsonl-status">${countLabel}${truncNote}</div>
|
|
114
|
+
${body}
|
|
115
|
+
</div>
|
|
116
|
+
`;
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
setupEvents(container, state, helpers) {
|
|
120
|
+
if (state.viewMode !== 'tree') return;
|
|
121
|
+
|
|
122
|
+
const ps = normalizeToolbarState(state.pluginState);
|
|
123
|
+
const toolbar = bindJsonToolbar(container, ps, helpers, '.jsonl-rows');
|
|
124
|
+
|
|
125
|
+
const rows = container.querySelector('.jsonl-rows');
|
|
126
|
+
if (rows) {
|
|
127
|
+
bindJsonTreeEvents(rows, { onToggle: toolbar.onToggle });
|
|
128
|
+
// Each row is its own document; the `L<n>` path prefix says which
|
|
129
|
+
// source line to re-parse (split cached, parse on demand).
|
|
130
|
+
let lines;
|
|
131
|
+
bindJsonTreeContextMenu(rows, {
|
|
132
|
+
rootLabel: S.preview?.json_menu?.copy_line || 'Copy whole line',
|
|
133
|
+
rootToast: S.preview?.json_menu?.copied_line || 'Line copied',
|
|
134
|
+
getRoot: (path) => {
|
|
135
|
+
const m = /^L(\d+)/.exec(path || '');
|
|
136
|
+
if (!m) return undefined;
|
|
137
|
+
if (!lines) lines = (state.content || '').split('\n');
|
|
138
|
+
try {
|
|
139
|
+
return JSON.parse(lines[Number(m[1]) - 1]);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
onSearch: toolbar.setQuery,
|
|
145
|
+
onSetExpanded: toolbar.record,
|
|
146
|
+
onAfterExpand: () => toolbar.refresh({ scroll: false }),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
toolbar.refresh({ scroll: false });
|
|
151
|
+
},
|
|
152
|
+
};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown preview plugin
|
|
3
|
+
*
|
|
4
|
+
* Handles: .md, .markdown
|
|
5
|
+
* Adds a "Rendered" view mode that renders markdown via MarkdownRenderer.
|
|
6
|
+
* Falls through to default code view for 'code' mode.
|
|
7
|
+
* Supports inline editing: click any block to edit its raw markdown in-place.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { MarkdownRenderer } from '../components.js';
|
|
11
|
+
import { setupInlineEdit, isInlineEditActive, setupCheckboxes } from '../preview/preview-inline-edit.js';
|
|
12
|
+
import { escapeHtml } from '../utils.js';
|
|
13
|
+
import S from '../strings.js';
|
|
14
|
+
|
|
15
|
+
let mdRenderer = null;
|
|
16
|
+
function getRenderer() {
|
|
17
|
+
if (!mdRenderer) mdRenderer = new MarkdownRenderer();
|
|
18
|
+
return mdRenderer;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
22
|
+
// YAML front matter — parsed into a friendly key/value panel instead
|
|
23
|
+
// of letting marked render the fences as <hr> + garbled heading.
|
|
24
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
25
|
+
|
|
26
|
+
const FRONT_MATTER_RE = /^---[ \t]*\r?\n([\s\S]*?)\r?\n(?:---|\.\.\.)[ \t]*(?:\r?\n|$)/;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Detect and parse a leading YAML front matter block.
|
|
30
|
+
* Light line-based parse: top-level `key: value` pairs, with any indented
|
|
31
|
+
* continuation lines (nested maps, lists, |/> scalars) kept as a raw block.
|
|
32
|
+
* Returns { fields, bodyOffset } or null when the file has no front matter.
|
|
33
|
+
*/
|
|
34
|
+
function parseFrontMatter(content) {
|
|
35
|
+
const m = content?.match(FRONT_MATTER_RE);
|
|
36
|
+
if (!m) return null;
|
|
37
|
+
const fields = [];
|
|
38
|
+
let current = null;
|
|
39
|
+
for (const line of m[1].split('\n')) {
|
|
40
|
+
const top = line.match(/^([A-Za-z0-9_][\w.-]*)\s*:\s?(.*)$/);
|
|
41
|
+
if (top) {
|
|
42
|
+
current = { key: top[1], value: top[2].trim(), block: [] };
|
|
43
|
+
fields.push(current);
|
|
44
|
+
} else if (current && (line.trim() === '' || /^\s/.test(line))) {
|
|
45
|
+
current.block.push(line);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!fields.length) return null;
|
|
49
|
+
return { fields, bodyOffset: m[0].length };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function unquote(s) {
|
|
53
|
+
const m = s.match(/^(['"])(.*)\1$/);
|
|
54
|
+
return m ? m[2] : s;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Extract list items from `[a, b]` flow or `- item` block syntax, else null. */
|
|
58
|
+
function frontMatterListItems(field) {
|
|
59
|
+
if (/^\[.*\]$/.test(field.value)) {
|
|
60
|
+
return field.value.slice(1, -1).split(',')
|
|
61
|
+
.map(s => unquote(s.trim())).filter(Boolean);
|
|
62
|
+
}
|
|
63
|
+
const nonEmpty = field.block.filter(l => l.trim());
|
|
64
|
+
if (field.value === '' && nonEmpty.length && nonEmpty.every(l => /^\s*-\s/.test(l))) {
|
|
65
|
+
return nonEmpty.map(l => unquote(l.replace(/^\s*-\s+/, '').trim()));
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function frontMatterValueHtml(field) {
|
|
71
|
+
const items = frontMatterListItems(field);
|
|
72
|
+
if (items) {
|
|
73
|
+
return items.map(it => `<span class="preview-fm-chip">${escapeHtml(it)}</span>`).join('');
|
|
74
|
+
}
|
|
75
|
+
const nonEmpty = field.block.filter(l => l.trim());
|
|
76
|
+
if (nonEmpty.length && (field.value === '' || /^[|>][+-]?\d*$/.test(field.value))) {
|
|
77
|
+
// Nested map or literal/folded scalar — show the dedented raw block
|
|
78
|
+
const indent = Math.min(...nonEmpty.map(l => l.match(/^\s*/)[0].length));
|
|
79
|
+
const block = field.block.map(l => l.slice(indent)).join('\n').replace(/\s+$/, '');
|
|
80
|
+
return `<pre class="preview-fm-block">${escapeHtml(block)}</pre>`;
|
|
81
|
+
}
|
|
82
|
+
return `<span class="preview-fm-value">${escapeHtml(unquote(field.value))}</span>`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function renderFrontMatterHtml(fm, collapsed) {
|
|
86
|
+
const rows = fm.fields.map(f => `
|
|
87
|
+
<div class="preview-fm-row">
|
|
88
|
+
<div class="preview-fm-key">${escapeHtml(f.key)}</div>
|
|
89
|
+
<div class="preview-fm-val">${frontMatterValueHtml(f)}</div>
|
|
90
|
+
</div>`).join('');
|
|
91
|
+
const title = S.preview?.front_matter_title || 'Front matter';
|
|
92
|
+
return `
|
|
93
|
+
<div class="preview-frontmatter ${collapsed ? 'collapsed' : ''}">
|
|
94
|
+
<button class="preview-fm-header" data-tooltip="${S.preview?.front_matter_toggle || 'Toggle front matter'}">
|
|
95
|
+
<svg class="preview-fm-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
|
96
|
+
<span class="preview-fm-title">${title}</span>
|
|
97
|
+
<span class="preview-fm-count">${fm.fields.length}</span>
|
|
98
|
+
</button>
|
|
99
|
+
<div class="preview-fm-fields">${rows}</div>
|
|
100
|
+
</div>`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Resolve a relative href/src against a base directory (absolute path).
|
|
105
|
+
* Strips query/hash, handles `.`/`..` segments. Returns the absolute
|
|
106
|
+
* path, or null when the result escapes the filesystem root.
|
|
107
|
+
*/
|
|
108
|
+
function resolveRelative(baseDir, rel) {
|
|
109
|
+
const parts = baseDir.split('/');
|
|
110
|
+
let clean = rel.split(/[?#]/)[0];
|
|
111
|
+
try { clean = decodeURIComponent(clean); } catch { /* keep raw */ }
|
|
112
|
+
for (const seg of clean.split('/')) {
|
|
113
|
+
if (!seg || seg === '.') continue;
|
|
114
|
+
if (seg === '..') parts.pop(); else parts.push(seg);
|
|
115
|
+
}
|
|
116
|
+
if (parts[0] !== '' || parts.length < 2) return null;
|
|
117
|
+
return parts.join('/');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Pencil icon SVG for the inline edit toggle
|
|
121
|
+
const pencilIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>`;
|
|
122
|
+
|
|
123
|
+
export default {
|
|
124
|
+
id: 'markdown',
|
|
125
|
+
|
|
126
|
+
match(path) {
|
|
127
|
+
const ext = path?.split('.').pop()?.toLowerCase();
|
|
128
|
+
return ext === 'md' || ext === 'markdown';
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
needsFetch: true,
|
|
132
|
+
editable: true,
|
|
133
|
+
|
|
134
|
+
viewModes: [{
|
|
135
|
+
mode: 'rendered',
|
|
136
|
+
label: 'Rendered',
|
|
137
|
+
icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>`,
|
|
138
|
+
}],
|
|
139
|
+
|
|
140
|
+
defaultViewMode: 'rendered',
|
|
141
|
+
|
|
142
|
+
initState() { return {}; },
|
|
143
|
+
|
|
144
|
+
renderBody(state, helpers) {
|
|
145
|
+
// Only handle rendered mode — code/edit modes fall through to core
|
|
146
|
+
if (state.viewMode !== 'rendered') return null;
|
|
147
|
+
|
|
148
|
+
const renderer = getRenderer();
|
|
149
|
+
const content = state.content || '';
|
|
150
|
+
|
|
151
|
+
// Front matter renders as a key/value panel; the markdown body is
|
|
152
|
+
// rendered from the remainder with offsets shifted so inline edits
|
|
153
|
+
// still map back into the full file content.
|
|
154
|
+
const fm = parseFrontMatter(content);
|
|
155
|
+
const fmHtml = fm ? renderFrontMatterHtml(fm, state.fmCollapsed) : '';
|
|
156
|
+
const body = fm ? content.slice(fm.bodyOffset) : content;
|
|
157
|
+
const renderedHtml = renderer.renderWithSourceMap(body, fm ? fm.bodyOffset : 0);
|
|
158
|
+
const isActive = isInlineEditActive();
|
|
159
|
+
|
|
160
|
+
return `
|
|
161
|
+
<div class="preview-body">
|
|
162
|
+
<div class="preview-rendered-wrapper">
|
|
163
|
+
${fmHtml}
|
|
164
|
+
<div class="preview-rendered markdown-content ${isActive ? 'inline-edit-mode' : ''}">${renderedHtml}</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
`;
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Toolbar contribution: the inline-edit toggle lives in the preview
|
|
172
|
+
* toolbar next to search/download rather than floating over the first
|
|
173
|
+
* rendered block, where it collided with headings and the per-block
|
|
174
|
+
* trash buttons and scrolled with the content.
|
|
175
|
+
*/
|
|
176
|
+
renderToolbarControls(state) {
|
|
177
|
+
if (state.viewMode !== 'rendered') return '';
|
|
178
|
+
const isActive = isInlineEditActive();
|
|
179
|
+
const tooltip = isActive
|
|
180
|
+
? (S.preview?.inline_edit_disable || 'Disable inline editing')
|
|
181
|
+
: (S.preview?.inline_edit_enable || 'Click to edit');
|
|
182
|
+
return `
|
|
183
|
+
<button class="inline-edit-toggle ${isActive ? 'active' : ''}" data-tooltip="${tooltip}">
|
|
184
|
+
${pencilIcon}
|
|
185
|
+
</button>
|
|
186
|
+
`;
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
setupEvents(container, state, helpers) {
|
|
190
|
+
// Markdown rendered view: process inline excalidraw/chart blocks
|
|
191
|
+
if (state.viewMode === 'rendered') {
|
|
192
|
+
MarkdownRenderer.processExcalidrawBlocks?.(container);
|
|
193
|
+
MarkdownRenderer.processChartBlocks?.(container);
|
|
194
|
+
setupInlineEdit(container);
|
|
195
|
+
setupCheckboxes(container);
|
|
196
|
+
|
|
197
|
+
const fmHeader = container.querySelector('.preview-fm-header');
|
|
198
|
+
fmHeader?.addEventListener('click', () => {
|
|
199
|
+
const panel = fmHeader.closest('.preview-frontmatter');
|
|
200
|
+
panel?.classList.toggle('collapsed');
|
|
201
|
+
state.fmCollapsed = panel?.classList.contains('collapsed');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Relative links (e.g. [notes](notes.md)) open in this preview,
|
|
205
|
+
// resolved against the current file's directory — the renderer's
|
|
206
|
+
// target="_blank" would 404 them against the app origin. Scheme'd
|
|
207
|
+
// URLs, //host, /absolute and #anchor links keep default behavior.
|
|
208
|
+
const rendered = container.querySelector('.preview-rendered');
|
|
209
|
+
|
|
210
|
+
// Relative image srcs (, <img src="…">) would
|
|
211
|
+
// resolve against the app origin and 404 — point them at
|
|
212
|
+
// /api/file-raw against the file's directory. On miss, retry
|
|
213
|
+
// against the session cwd: README section sources reference
|
|
214
|
+
// repo-root-relative paths that only compose correctly at root.
|
|
215
|
+
if (rendered) {
|
|
216
|
+
const fileDir = (state.currentPath || '').split('/').slice(0, -1).join('/');
|
|
217
|
+
for (const img of rendered.querySelectorAll('img[src]')) {
|
|
218
|
+
const src = img.getAttribute('src') || '';
|
|
219
|
+
if (/^([a-z][a-z0-9+.-]*:|\/\/|\/)/i.test(src)) continue;
|
|
220
|
+
const abs = resolveRelative(fileDir, src);
|
|
221
|
+
if (!abs) continue;
|
|
222
|
+
img.src = `/api/file-raw?path=${encodeURIComponent(abs)}`;
|
|
223
|
+
const rootAbs = state.cwd ? resolveRelative(state.cwd, src) : null;
|
|
224
|
+
if (rootAbs && rootAbs !== abs) {
|
|
225
|
+
img.addEventListener('error', () => {
|
|
226
|
+
img.src = `/api/file-raw?path=${encodeURIComponent(rootAbs)}`;
|
|
227
|
+
}, { once: true });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
rendered?.addEventListener('click', async (e) => {
|
|
233
|
+
if (isInlineEditActive(rendered)) return;
|
|
234
|
+
const a = e.target.closest('a[href]');
|
|
235
|
+
if (!a || !rendered.contains(a)) return;
|
|
236
|
+
const href = a.getAttribute('href') || '';
|
|
237
|
+
if (href.startsWith('#')) {
|
|
238
|
+
// In-page anchors: marked runs with headerIds:false and
|
|
239
|
+
// target=_blank, so default behavior opens a dead tab.
|
|
240
|
+
// Resolve the slug against heading text ourselves — same
|
|
241
|
+
// algorithm as compose.sh's slug() (keep them in sync).
|
|
242
|
+
e.preventDefault();
|
|
243
|
+
let want; try { want = decodeURIComponent(href.slice(1)); } catch { want = href.slice(1); }
|
|
244
|
+
const seen = new Map();
|
|
245
|
+
for (const h of rendered.querySelectorAll('h1,h2,h3,h4,h5,h6')) {
|
|
246
|
+
let s = h.textContent.trim().toLowerCase()
|
|
247
|
+
.replace(/[^\p{L}\p{N}_\s-]/gu, '')
|
|
248
|
+
.replace(/\s+/g, '-').replace(/^-+|-+$/g, '');
|
|
249
|
+
const n = seen.get(s) || 0;
|
|
250
|
+
seen.set(s, n + 1);
|
|
251
|
+
if (n) s = `${s}-${n}`;
|
|
252
|
+
if (s === want) { h.scrollIntoView({ behavior: 'smooth', block: 'start' }); break; }
|
|
253
|
+
}
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (/^([a-z][a-z0-9+.-]*:|\/\/|\/)/i.test(href)) return;
|
|
257
|
+
e.preventDefault();
|
|
258
|
+
const fileDir = (state.currentPath || '').split('/').slice(0, -1).join('/');
|
|
259
|
+
const target = resolveRelative(fileDir, href);
|
|
260
|
+
if (!target) return; // escaped filesystem root
|
|
261
|
+
const { FilePreviewWidget } = await import('../widgets/file-preview-widget.js');
|
|
262
|
+
FilePreviewWidget.preview(target);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panzoom plugin factory
|
|
3
|
+
*
|
|
4
|
+
* Creates plugins for file types that render as images in a panzoom canvas.
|
|
5
|
+
* Used by: image, excalidraw, chart plugins.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { CONFIG, escapeHtml, panzoomToolbarHtml, setupPanZoom, darkDefault } from './plugin-helpers.js';
|
|
9
|
+
import { basename } from '../path-utils.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {object} opts
|
|
13
|
+
* @param {string} opts.id - Plugin ID
|
|
14
|
+
* @param {Function} opts.match - (path) => boolean
|
|
15
|
+
* @param {string} opts.canvasClass - CSS class for the panzoom canvas container
|
|
16
|
+
* @param {string} opts.imgClass - CSS class for the image element
|
|
17
|
+
* @param {boolean} opts.hasDarkToggle - Show dark/light toggle button
|
|
18
|
+
* @param {Function} opts.getSrc - (path, pluginState) => image URL string
|
|
19
|
+
* @param {string} [opts.bodyClass] - Additional class on preview-body (e.g. 'preview-excalidraw-body')
|
|
20
|
+
*/
|
|
21
|
+
export function createPanzoomPlugin({ id, match, canvasClass, imgClass, hasDarkToggle, getSrc, bodyClass }) {
|
|
22
|
+
return {
|
|
23
|
+
id,
|
|
24
|
+
match,
|
|
25
|
+
needsFetch: false,
|
|
26
|
+
editable: false,
|
|
27
|
+
|
|
28
|
+
initState() {
|
|
29
|
+
const s = { scale: 1, tx: 0, ty: 0 };
|
|
30
|
+
if (hasDarkToggle) s.isDark = darkDefault;
|
|
31
|
+
return s;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
renderBody(state, helpers) {
|
|
35
|
+
const ps = state.pluginState;
|
|
36
|
+
const src = getSrc(state.currentPath, ps);
|
|
37
|
+
const fileName = basename(state.currentPath) || '';
|
|
38
|
+
const bodyClasses = ['preview-body', canvasClass];
|
|
39
|
+
if (bodyClass) bodyClasses.push(bodyClass);
|
|
40
|
+
bodyClasses.push('panzoom-canvas');
|
|
41
|
+
|
|
42
|
+
return `
|
|
43
|
+
${panzoomToolbarHtml(ps, { hasDarkToggle })}
|
|
44
|
+
<div class="${bodyClasses.join(' ')}" data-bg="${ps.isDark ? 'dark' : 'light'}">
|
|
45
|
+
<img src="${src}" alt="${escapeHtml(fileName)}" class="${imgClass} panzoom-img" />
|
|
46
|
+
</div>
|
|
47
|
+
`;
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
setupEvents(container, state, helpers) {
|
|
51
|
+
const ps = state.pluginState;
|
|
52
|
+
setupPanZoom(container, ps, {
|
|
53
|
+
canvasSelector: `.${canvasClass}`,
|
|
54
|
+
imgSelector: `.${imgClass}`,
|
|
55
|
+
stateCheck: () => state.plugin?.id === id,
|
|
56
|
+
onDarkToggle: hasDarkToggle ? () => {
|
|
57
|
+
ps.isDark = !ps.isDark;
|
|
58
|
+
helpers.rerenderContent();
|
|
59
|
+
} : undefined,
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|