opencode-preview-plus 0.13.1 → 0.14.0

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.en.md ADDED
@@ -0,0 +1,215 @@
1
+ <p align="center">
2
+ <h1 align="center">opencode-preview-plus</h1>
3
+ <p align="center">
4
+ Instant file preview for <a href="https://github.com/opencode-ai/opencode">OpenCode</a> — Markdown, DrawIO, HTML, CSV, and 40+ code languages.<br>
5
+ Zero config. Live reload. Dark mode. Just works.
6
+ </p>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://github.com/mike652638/opencode-preview-plus/releases"><img src="https://img.shields.io/github/v/release/mike652638/opencode-preview-plus?style=flat-square&color=blue" alt="Release"></a>
11
+ <a href="https://github.com/mike652638/opencode-preview-plus/blob/main/LICENSE"><img src="https://img.shields.io/github/license/mike652638/opencode-preview-plus?style=flat-square" alt="License"></a>
12
+ <a href="https://bun.sh"><img src="https://img.shields.io/badge/runtime-Bun-f9f1e1?style=flat-square&logo=bun" alt="Bun"></a>
13
+ </p>
14
+
15
+ ---
16
+
17
+ [中文文档](./README.md)
18
+
19
+ ## Screenshots
20
+
21
+ **Command palette quick preview**
22
+
23
+ ![Command palette](https://raw.githubusercontent.com/mike652638/opencode-preview-plus/main/assets/preview-1.png)
24
+
25
+ **AI-powered preview tool invocation**
26
+
27
+ ![AI preview invocation](https://raw.githubusercontent.com/mike652638/opencode-preview-plus/main/assets/preview-2.png)
28
+
29
+ **Browser preview UI (file tree + TOC + Markdown rendering)**
30
+
31
+ ![Browser preview UI](https://raw.githubusercontent.com/mike652638/opencode-preview-plus/main/assets/preview-3.png)
32
+
33
+ ## Why?
34
+
35
+ Terminal AI editors are great for coding, but previewing files means switching to another app. **opencode-preview-plus** brings the preview right to your browser — auto-started, auto-refreshed, zero config.
36
+
37
+ **One line to install:**
38
+
39
+ ```json
40
+ { "plugin": ["mike652638/opencode-preview-plus"] }
41
+ ```
42
+
43
+ That's it. Open OpenCode, and preview is ready.
44
+
45
+ ## Features
46
+
47
+ ### File Rendering
48
+
49
+ | Format | Description |
50
+ |---|---|
51
+ | **Markdown** | GFM rendering via [marked](https://github.com/markedjs/marked), syntax-highlighted code blocks, word count, reading time estimate, auto-generated Table of Contents |
52
+ | **DrawIO** | Embedded [draw.io](https://www.drawio.com/) viewer with multi-page support, zoom, layers, and page navigation |
53
+ | **HTML** | Sandboxed iframe preview with "Open in new tab" link |
54
+ | **CSV** | Tabular rendering with rainbow-striped rows, row/column stats |
55
+ | **PNG** | Minimal image preview with max-dimensions, centering, and original download link |
56
+ | **Code** | Syntax highlighting for 40+ languages via [highlight.js](https://highlightjs.org/) — TypeScript, Python, Rust, Go, Java, Kotlin, C/C++, Ruby, PHP, Swift, Scala, Zig, Elixir, Erlang, Haskell, OCaml, Lua, R, SQL, GraphQL, Protobuf, HCL, Vue, Svelte, and more |
57
+
58
+ ### Browser UI
59
+
60
+ | Feature | Description |
61
+ |---|---|
62
+ | **SPA Navigation** | Single-page app with client-side routing — no full reloads between files |
63
+ | **Tabbed Interface** | Open multiple files in tabs, switch between them, close individually. Tab state persists across reloads via localStorage |
64
+ | **File Tree Sidebar** | Collapsible folder tree with file-type icons, remembers open/closed folder state |
65
+ | **Resizable Sidebar** | Drag-to-resize sidebar width, persisted across sessions |
66
+ | **Markdown TOC** | Auto-generated "On This Page" table of contents with scroll-tracking active headings |
67
+ | **Worktree Switcher** | Dropdown in the sidebar to switch between git worktrees |
68
+ | **Copy Path** | One-click copy of the project root directory path |
69
+ | **Live Reload** | WebSocket-based auto-refresh on file save — all open tabs reload simultaneously |
70
+ | **Dark Mode** | Follows system `prefers-color-scheme` preference |
71
+
72
+ ### Multi-Project
73
+
74
+ | Feature | Description |
75
+ |---|---|
76
+ | **Project Discovery** | Automatically discovers all projects registered in the running OpenCode instance via its API |
77
+ | **Project List** | Root page (`/`) shows all projects with search filtering and jump-to dropdown |
78
+ | **URL Isolation** | Each project is accessed via `?project=<id>` query parameter |
79
+
80
+ ### OpenCode Integration
81
+
82
+ | Feature | Description |
83
+ |---|---|
84
+ | **Server Plugin** | Exposes a `preview` tool that the AI agent can call to open any previewable file in the browser |
85
+ | **TUI Plugin** | Sidebar widget showing previewable changed files + command palette entries |
86
+ | **Auto-Start** | Preview server starts in the background when OpenCode launches — non-blocking |
87
+ | **File Events** | Listens to `file.edited` events from OpenCode for logging |
88
+ | **Path Security** | Only serves files within the project directory — path traversal is blocked |
89
+
90
+ ## Quick Start
91
+
92
+ ### As OpenCode Plugin (Recommended)
93
+
94
+ Add to your `opencode.json`:
95
+
96
+ ```json
97
+ {
98
+ "plugin": ["mike652638/opencode-preview-plus"]
99
+ }
100
+ ```
101
+
102
+ Or pin a specific version:
103
+
104
+ ```json
105
+ {
106
+ "plugin": ["mike652638/opencode-preview-plus@v0.13.0"]
107
+ }
108
+ ```
109
+
110
+ The preview server starts automatically when OpenCode launches. A `preview` tool becomes available for the AI to open files in your browser.
111
+
112
+ ### As Local Plugin
113
+
114
+ ```bash
115
+ git clone https://github.com/mike652638/opencode-preview-plus.git
116
+ ln -s $(pwd)/opencode-preview-plus .opencode/plugins/opencode-preview-plus
117
+ ```
118
+
119
+ ### Standalone Server
120
+
121
+ Run the preview server directly without OpenCode:
122
+
123
+ ```bash
124
+ cd opencode-preview-plus
125
+ bun install
126
+ bun run dev # Start server
127
+ ```
128
+
129
+ Set `OPENCODE_SERVER_URL` to enable project auto-discovery:
130
+
131
+ ```bash
132
+ OPENCODE_SERVER_URL=http://localhost:10013 bun run dev
133
+ ```
134
+
135
+ Then open `http://localhost:17890` in your browser.
136
+
137
+ ## Configuration
138
+
139
+ | Environment Variable | Default | Description |
140
+ |---|---|---|
141
+ | `PREVIEW_PORT` | `17890` | Server port |
142
+ | `PREVIEW_HOST` | `localhost` | Hostname used in generated preview URLs. Set to your remote machine's hostname or IP when accessing the preview from a different machine (e.g. SSH remote) |
143
+ | `PREVIEW_MAX_TABS` | `10` | Maximum number of open tabs in the browser UI |
144
+ | `OPENCODE_SERVER_URL` | — | OpenCode server URL for project discovery (standalone mode only; auto-configured when running as plugin) |
145
+
146
+ ## Architecture
147
+
148
+ ```
149
+ src/
150
+ ├── index.ts # OpenCode server plugin entry — registers preview tool + file events
151
+ ├── tui.tsx # OpenCode TUI plugin — sidebar widget + command palette
152
+ ├── server.ts # HTTP server (node:http) + WebSocket (ws) + file watcher + SPA shell
153
+ ├── renderers/
154
+ │ ├── code.ts # Syntax-highlighted code preview (40+ languages)
155
+ │ ├── csv.ts # CSV table renderer with rainbow rows
156
+ │ ├── drawio.ts # draw.io viewer (CDN)
157
+ │ ├── html.ts # Sandboxed iframe HTML preview
158
+ │ └── markdown.ts # GFM rendering via marked + word count + reading time
159
+ └── templates/
160
+ ├── browser.html # Legacy standalone browser template
161
+ └── styles.css # Shared styles (light/dark themes)
162
+ ```
163
+
164
+ ## API Routes
165
+
166
+ All project-scoped routes require a `?project=<id>` query parameter. Worktree routes additionally accept `?worktree=<name>`.
167
+
168
+ | Route | Description |
169
+ |---|---|
170
+ | `GET /` | Project list page (search + jump-to) |
171
+ | `GET /browse` | File browser SPA shell for a project |
172
+ | `GET /preview?file=<path>` | Preview a file (Markdown, DrawIO, HTML, CSV, or code) |
173
+ | `GET /api/projects` | JSON list of all discovered projects |
174
+ | `GET /api/files` | JSON list of previewable files in a project |
175
+ | `GET /api/file?path=<path>` | Raw file content |
176
+ | `GET /api/render?file=<path>` | Rendered content fragment (JSON: `{ title, body, contentClass }`) |
177
+ | `GET /api/worktrees` | JSON list of git worktrees for a project |
178
+ | `GET /styles.css` | Stylesheet |
179
+ | `WS /ws` | Live reload notifications |
180
+
181
+ ## Supported Languages
182
+
183
+ The code renderer recognizes 40+ file extensions and special filenames:
184
+
185
+ <details>
186
+ <summary>Full list</summary>
187
+
188
+ **By extension**: `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.py`, `.rs`, `.go`, `.java`, `.kt`, `.c`, `.cpp`, `.h`, `.hpp`, `.cs`, `.rb`, `.php`, `.swift`, `.sh`, `.bash`, `.zsh`, `.fish`, `.sql`, `.css`, `.scss`, `.less`, `.json`, `.yaml`, `.yml`, `.toml`, `.xml`, `.graphql`, `.gql`, `.proto`, `.dockerfile`, `.lua`, `.r`, `.scala`, `.zig`, `.ex`, `.exs`, `.erl`, `.hs`, `.ml`, `.vue`, `.svelte`, `.tf`, `.ini`, `.conf`, `.env`, `.gitignore`, `.editorconfig`
189
+
190
+ **By filename**: `Dockerfile`, `Makefile`, `Jenkinsfile`, `Vagrantfile`, `Gemfile`, `Rakefile`, `Justfile`
191
+
192
+ </details>
193
+
194
+ ## Requirements
195
+
196
+ - [Bun](https://bun.sh) v1.0+
197
+ - OpenCode v1.3+ (for plugin usage)
198
+
199
+ > **Note**: This package exports `.ts`/`.tsx` entry points directly and requires Bun as the runtime. It is not compatible with plain Node.js.
200
+
201
+ ## Contributing
202
+
203
+ Issues and PRs welcome! This project uses Bun for development:
204
+
205
+ ```bash
206
+ git clone https://github.com/mike652638/opencode-preview-plus.git
207
+ cd opencode-preview-plus
208
+ bun install
209
+ bun test
210
+ bun run dev
211
+ ```
212
+
213
+ ## License
214
+
215
+ [MIT](LICENSE)
package/README.md CHANGED
@@ -1,199 +1,215 @@
1
- <p align="center">
2
- <h1 align="center">opencode-preview-plus</h1>
3
- <p align="center">
4
- Instant file preview for <a href="https://github.com/opencode-ai/opencode">OpenCode</a> — Markdown, DrawIO, HTML, CSV, and 40+ code languages.<br>
5
- Zero config. Live reload. Dark mode. Just works.
6
- </p>
7
- </p>
8
-
9
- <p align="center">
10
- <a href="https://github.com/mike652638/opencode-preview-plus/releases"><img src="https://img.shields.io/github/v/release/mike652638/opencode-preview-plus?style=flat-square&color=blue" alt="Release"></a>
11
- <a href="https://github.com/mike652638/opencode-preview-plus/blob/main/LICENSE"><img src="https://img.shields.io/github/license/mike652638/opencode-preview-plus?style=flat-square" alt="License"></a>
12
- <a href="https://bun.sh"><img src="https://img.shields.io/badge/runtime-Bun-f9f1e1?style=flat-square&logo=bun" alt="Bun"></a>
13
- </p>
14
-
15
- ---
16
-
17
- ## Why?
18
-
19
- Terminal AI editors are great for coding, but previewing files means switching to another app. **opencode-preview** brings the preview right to your browser — auto-started, auto-refreshed, zero config.
20
-
21
- **One line to install:**
22
-
23
- ```json
24
- { "plugin": ["mike652638/opencode-preview-plus"] }
25
- ```
26
-
27
- That's it. Open OpenCode, and preview is ready.
28
-
29
- ## Features
30
-
31
- ### File Rendering
32
-
33
- | Format | Description |
34
- |---|---|
35
- | **Markdown** | GFM rendering via [marked](https://github.com/markedjs/marked), syntax-highlighted code blocks, word count, reading time estimate, auto-generated Table of Contents |
36
- | **DrawIO** | Embedded [draw.io](https://www.drawio.com/) viewer with multi-page support, zoom, layers, and page navigation |
37
- | **HTML** | Sandboxed iframe preview with "Open in new tab" link |
38
- | **CSV** | Tabular rendering with rainbow-striped rows, row/column stats |
39
- | **PNG** | Minimal image preview with max-dimensions, centering, and original download link |
40
- | **Code** | Syntax highlighting for 40+ languages via [highlight.js](https://highlightjs.org/) — TypeScript, Python, Rust, Go, Java, Kotlin, C/C++, Ruby, PHP, Swift, Scala, Zig, Elixir, Erlang, Haskell, OCaml, Lua, R, SQL, GraphQL, Protobuf, HCL, Vue, Svelte, and more |
41
-
42
- ### Browser UI
43
-
44
- | Feature | Description |
45
- |---|---|
46
- | **SPA Navigation** | Single-page app with client-side routing — no full reloads between files |
47
- | **Tabbed Interface** | Open multiple files in tabs, switch between them, close individually. Tab state persists across reloads via localStorage |
48
- | **File Tree Sidebar** | Collapsible folder tree with file-type icons, remembers open/closed folder state |
49
- | **Resizable Sidebar** | Drag-to-resize sidebar width, persisted across sessions |
50
- | **Markdown TOC** | Auto-generated "On This Page" table of contents with scroll-tracking active headings |
51
- | **Worktree Switcher** | Dropdown in the sidebar to switch between git worktrees |
52
- | **Copy Path** | One-click copy of the project root directory path |
53
- | **Live Reload** | WebSocket-based auto-refresh on file save — all open tabs reload simultaneously |
54
- | **Dark Mode** | Follows system `prefers-color-scheme` preference |
55
-
56
- ### Multi-Project
57
-
58
- | Feature | Description |
59
- |---|---|
60
- | **Project Discovery** | Automatically discovers all projects registered in the running OpenCode instance via its API |
61
- | **Project List** | Root page (`/`) shows all projects with search filtering and jump-to dropdown |
62
- | **URL Isolation** | Each project is accessed via `?project=<id>` query parameter |
63
-
64
- ### OpenCode Integration
65
-
66
- | Feature | Description |
67
- |---|---|
68
- | **Server Plugin** | Exposes a `preview` tool that the AI agent can call to open any previewable file in the browser |
69
- | **TUI Plugin** | Sidebar widget showing previewable changed files + command palette entries |
70
- | **Auto-Start** | Preview server starts in the background when OpenCode launches — non-blocking |
71
- | **File Events** | Listens to `file.edited` events from OpenCode for logging |
72
- | **Path Security** | Only serves files within the project directory — path traversal is blocked |
73
-
74
- ## Quick Start
75
-
76
- ### As OpenCode Plugin (Recommended)
77
-
78
- Add to your `opencode.json`:
79
-
80
- ```json
81
- {
82
- "plugin": ["mike652638/opencode-preview-plus"]
83
- }
84
- ```
85
-
86
- Or pin a specific version:
87
-
88
- ```json
89
- {
90
- "plugin": ["mike652638/opencode-preview-plus@v0.13.0"]
91
- }
92
- ```
93
-
94
- The preview server starts automatically when OpenCode launches. A `preview` tool becomes available for the AI to open files in your browser.
95
-
96
- ### As Local Plugin
97
-
98
- ```bash
99
- git clone https://github.com/mike652638/opencode-preview-plus.git
100
- ln -s $(pwd)/opencode-preview-plus .opencode/plugins/opencode-preview-plus
101
- ```
102
-
103
- ### Standalone Server
104
-
105
- Run the preview server directly without OpenCode:
106
-
107
- ```bash
108
- cd opencode-preview-plus
109
- bun install
110
- bun run dev # Start server
111
- ```
112
-
113
- Set `OPENCODE_SERVER_URL` to enable project auto-discovery:
114
-
115
- ```bash
116
- OPENCODE_SERVER_URL=http://localhost:10013 bun run dev
117
- ```
118
-
119
- Then open `http://localhost:17890` in your browser.
120
-
121
- ## Configuration
122
-
123
- | Environment Variable | Default | Description |
124
- |---|---|---|
125
- | `PREVIEW_PORT` | `17890` | Server port |
126
- | `PREVIEW_HOST` | `localhost` | Hostname used in generated preview URLs. Set to your remote machine's hostname or IP when accessing the preview from a different machine (e.g. SSH remote) |
127
- | `PREVIEW_MAX_TABS` | `10` | Maximum number of open tabs in the browser UI |
128
- | `OPENCODE_SERVER_URL` | — | OpenCode server URL for project discovery (standalone mode only; auto-configured when running as plugin) |
129
-
130
- ## Architecture
131
-
132
- ```
133
- src/
134
- ├── index.ts # OpenCode server plugin entry — registers preview tool + file events
135
- ├── tui.tsx # OpenCode TUI plugin — sidebar widget + command palette
136
- ├── server.ts # HTTP server (node:http) + WebSocket (ws) + file watcher + SPA shell
137
- ├── renderers/
138
- │ ├── code.ts # Syntax-highlighted code preview (40+ languages)
139
- │ ├── csv.ts # CSV table renderer with rainbow rows
140
- │ ├── drawio.ts # draw.io viewer (CDN)
141
- │ ├── html.ts # Sandboxed iframe HTML preview
142
- │ └── markdown.ts # GFM rendering via marked + word count + reading time
143
- └── templates/
144
- ├── browser.html # Legacy standalone browser template
145
- └── styles.css # Shared styles (light/dark themes)
146
- ```
147
-
148
- ## API Routes
149
-
150
- All project-scoped routes require a `?project=<id>` query parameter. Worktree routes additionally accept `?worktree=<name>`.
151
-
152
- | Route | Description |
153
- |---|---|
154
- | `GET /` | Project list page (search + jump-to) |
155
- | `GET /browse` | File browser SPA shell for a project |
156
- | `GET /preview?file=<path>` | Preview a file (Markdown, DrawIO, HTML, CSV, or code) |
157
- | `GET /api/projects` | JSON list of all discovered projects |
158
- | `GET /api/files` | JSON list of previewable files in a project |
159
- | `GET /api/file?path=<path>` | Raw file content |
160
- | `GET /api/render?file=<path>` | Rendered content fragment (JSON: `{ title, body, contentClass }`) |
161
- | `GET /api/worktrees` | JSON list of git worktrees for a project |
162
- | `GET /styles.css` | Stylesheet |
163
- | `WS /ws` | Live reload notifications |
164
-
165
- ## Supported Languages
166
-
167
- The code renderer recognizes 40+ file extensions and special filenames:
168
-
169
- <details>
170
- <summary>Full list</summary>
171
-
172
- **By extension**: `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.py`, `.rs`, `.go`, `.java`, `.kt`, `.c`, `.cpp`, `.h`, `.hpp`, `.cs`, `.rb`, `.php`, `.swift`, `.sh`, `.bash`, `.zsh`, `.fish`, `.sql`, `.css`, `.scss`, `.less`, `.json`, `.yaml`, `.yml`, `.toml`, `.xml`, `.graphql`, `.gql`, `.proto`, `.dockerfile`, `.lua`, `.r`, `.scala`, `.zig`, `.ex`, `.exs`, `.erl`, `.hs`, `.ml`, `.vue`, `.svelte`, `.tf`, `.ini`, `.conf`, `.env`, `.gitignore`, `.editorconfig`
173
-
174
- **By filename**: `Dockerfile`, `Makefile`, `Jenkinsfile`, `Vagrantfile`, `Gemfile`, `Rakefile`, `Justfile`
175
-
176
- </details>
177
-
178
- ## Requirements
179
-
180
- - [Bun](https://bun.sh) v1.0+
181
- - OpenCode v1.3+ (for plugin usage)
182
-
183
- > **Note**: This package exports `.ts`/`.tsx` entry points directly and requires Bun as the runtime. It is not compatible with plain Node.js.
184
-
185
- ## Contributing
186
-
187
- Issues and PRs welcome! This project uses Bun for development:
188
-
189
- ```bash
190
- git clone https://github.com/mike652638/opencode-preview-plus.git
191
- cd opencode-preview-plus
192
- bun install
193
- bun test
194
- bun run dev
195
- ```
196
-
197
- ## License
198
-
199
- [MIT](LICENSE)
1
+ <p align="center">
2
+ <h1 align="center">opencode-preview-plus</h1>
3
+ <p align="center">
4
+ <a href="https://github.com/opencode-ai/opencode">OpenCode</a> 提供即时文件预览 — MarkdownDrawIOHTMLCSV 40+ 种编程语言。<br>
5
+ 零配置。实时刷新。深色模式。开箱即用。
6
+ </p>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://github.com/mike652638/opencode-preview-plus/releases"><img src="https://img.shields.io/github/v/release/mike652638/opencode-preview-plus?style=flat-square&color=blue" alt="Release"></a>
11
+ <a href="https://github.com/mike652638/opencode-preview-plus/blob/main/LICENSE"><img src="https://img.shields.io/github/license/mike652638/opencode-preview-plus?style=flat-square" alt="License"></a>
12
+ <a href="https://bun.sh"><img src="https://img.shields.io/badge/runtime-Bun-f9f1e1?style=flat-square&logo=bun" alt="Bun"></a>
13
+ </p>
14
+
15
+ ---
16
+
17
+ [English](https://github.com/mike652638/opencode-preview-plus/blob/main/README.en.md)
18
+
19
+ ## 效果预览
20
+
21
+ **指令面板快速预览**
22
+
23
+ ![指令面板](https://raw.githubusercontent.com/mike652638/opencode-preview-plus/main/assets/preview-1.png)
24
+
25
+ **AI 智能调用预览工具**
26
+
27
+ ![AI 调用预览](https://raw.githubusercontent.com/mike652638/opencode-preview-plus/main/assets/preview-2.png)
28
+
29
+ **浏览器预览界面(文件树 + 目录大纲 + Markdown 渲染)**
30
+
31
+ ![浏览器预览界面](https://raw.githubusercontent.com/mike652638/opencode-preview-plus/main/assets/preview-3.png)
32
+
33
+ ## 为什么需要?
34
+
35
+ 终端 AI 编辑器非常适合编码,但预览文件需要切换到其他应用。**opencode-preview-plus** 将预览直接带到你的浏览器中 自动启动、自动刷新、零配置。
36
+
37
+ **一行安装:**
38
+
39
+ ```json
40
+ { "plugin": ["mike652638/opencode-preview-plus"] }
41
+ ```
42
+
43
+ 就这样。打开 OpenCode,预览即可使用。
44
+
45
+ ## 功能特性
46
+
47
+ ### 文件渲染
48
+
49
+ | 格式 | 说明 |
50
+ |---|---|
51
+ | **Markdown** | 基于 [marked](https://github.com/markedjs/marked) GFM 渲染,代码块语法高亮,字数统计,阅读时长估算,自动生成目录 |
52
+ | **DrawIO** | 嵌入式 [draw.io](https://www.drawio.com/) 查看器,支持多页、缩放、图层和页面导航 |
53
+ | **HTML** | 沙箱 iframe 预览,支持"在新标签页中打开" |
54
+ | **CSV** | 表格渲染,彩虹条纹行,行列统计 |
55
+ | **PNG** | 图片预览,自动缩放居中,提供原始文件下载链接 |
56
+ | **代码** | 通过 [highlight.js](https://highlightjs.org/) 支持 40+ 种语言语法高亮 — TypeScript、Python、Rust、Go、Java、Kotlin、C/C++、Ruby、PHP、Swift、Scala、Zig、Elixir、Erlang、Haskell、OCaml、Lua、R、SQL、GraphQL、Protobuf、HCL、Vue、Svelte 等 |
57
+
58
+ ### 浏览器界面
59
+
60
+ | 功能 | 说明 |
61
+ |---|---|
62
+ | **SPA 导航** | 单页应用,客户端路由 文件切换无需整页刷新 |
63
+ | **标签页界面** | 多文件标签页打开、切换、关闭,状态通过 localStorage 持久化 |
64
+ | **文件树侧边栏** | 可折叠文件夹树,带文件类型图标,记忆文件夹展开/折叠状态 |
65
+ | **可调侧边栏** | 拖拽调整侧边栏宽度,跨会话持久化 |
66
+ | **Markdown 目录** | 自动生成"本页目录",滚动时高亮当前标题 |
67
+ | **工作树切换器** | 侧边栏下拉菜单切换 Git 工作树 |
68
+ | **复制路径** | 一键复制项目根目录路径 |
69
+ | **实时刷新** | 基于 WebSocket 的文件保存自动刷新 所有打开的标签页同步更新 |
70
+ | **深色模式** | 跟随系统 `prefers-color-scheme` 偏好 |
71
+
72
+ ### 多项目
73
+
74
+ | 功能 | 说明 |
75
+ |---|---|
76
+ | **项目发现** | 通过 API 自动发现运行中的 OpenCode 实例注册的所有项目 |
77
+ | **项目列表** | 根页面(`/`)显示所有项目,支持搜索筛选和快速跳转 |
78
+ | **URL 隔离** | 每个项目通过 `?project=<id>` 查询参数访问 |
79
+
80
+ ### OpenCode 集成
81
+
82
+ | 功能 | 说明 |
83
+ |---|---|
84
+ | **Server Plugin** | 暴露 `preview` 工具,AI 代理可调用它在浏览器中打开任意可预览文件 |
85
+ | **TUI Plugin** | 侧边栏组件显示可预览的变更文件 + 命令面板条目 |
86
+ | **自动启动** | OpenCode 启动时预览服务器在后台自动启动 — 非阻塞 |
87
+ | **文件事件** | 监听 OpenCode 的 `file.edited` 事件用于日志记录 |
88
+ | **路径安全** | 仅提供项目目录内的文件 — 路径遍历攻击被阻止 |
89
+
90
+ ## 快速开始
91
+
92
+ ### 作为 OpenCode 插件(推荐)
93
+
94
+ `opencode.json` 中添加:
95
+
96
+ ```json
97
+ {
98
+ "plugin": ["mike652638/opencode-preview-plus"]
99
+ }
100
+ ```
101
+
102
+ 或固定特定版本:
103
+
104
+ ```json
105
+ {
106
+ "plugin": ["mike652638/opencode-preview-plus@v0.13.0"]
107
+ }
108
+ ```
109
+
110
+ OpenCode 启动时预览服务器会自动运行。`preview` 工具将可供 AI 在浏览器中打开文件。
111
+
112
+ ### 作为本地插件
113
+
114
+ ```bash
115
+ git clone https://github.com/mike652638/opencode-preview-plus.git
116
+ ln -s $(pwd)/opencode-preview-plus .opencode/plugins/opencode-preview-plus
117
+ ```
118
+
119
+ ### 独立服务器
120
+
121
+ 无需 OpenCode 即可直接运行预览服务器:
122
+
123
+ ```bash
124
+ cd opencode-preview-plus
125
+ bun install
126
+ bun run dev # 启动服务器
127
+ ```
128
+
129
+ 设置 `OPENCODE_SERVER_URL` 以启用项目自动发现:
130
+
131
+ ```bash
132
+ OPENCODE_SERVER_URL=http://localhost:10013 bun run dev
133
+ ```
134
+
135
+ 然后在浏览器中打开 `http://localhost:17890`。
136
+
137
+ ## 配置
138
+
139
+ | 环境变量 | 默认值 | 说明 |
140
+ |---|---|---|
141
+ | `PREVIEW_PORT` | `17890` | 服务器端口 |
142
+ | `PREVIEW_HOST` | `localhost` | 生成预览 URL 时使用的主机名。从远程机器访问预览时(如 SSH),设置为远程机器的主机名或 IP |
143
+ | `PREVIEW_MAX_TABS` | `10` | 浏览器界面中最大打开标签页数 |
144
+ | `OPENCODE_SERVER_URL` | | OpenCode 服务器 URL,用于项目发现(仅独立模式;作为插件运行时自动配置) |
145
+
146
+ ## 架构
147
+
148
+ ```
149
+ src/
150
+ ├── index.ts # OpenCode server plugin 入口 注册 preview 工具 + 文件事件
151
+ ├── tui.tsx # OpenCode TUI plugin — 侧边栏组件 + 命令面板
152
+ ├── server.ts # HTTP 服务器 (node:http) + WebSocket (ws) + 文件监听 + SPA 壳
153
+ ├── renderers/
154
+ │ ├── code.ts # 语法高亮代码预览(40+ 种语言)
155
+ │ ├── csv.ts # CSV 表格渲染器(彩虹行)
156
+ │ ├── drawio.ts # draw.io 查看器(CDN)
157
+ │ ├── html.ts # 沙箱 iframe HTML 预览
158
+ │ └── markdown.ts # 基于 marked GFM 渲染 + 字数统计 + 阅读时长
159
+ └── templates/
160
+ ├── browser.html # 传统独立浏览器模板
161
+ └── styles.css # 共享样式(深色/浅色主题)
162
+ ```
163
+
164
+ ## API 路由
165
+
166
+ 所有项目范围的路由需要 `?project=<id>` 查询参数。工作树路由额外接受 `?worktree=<name>`。
167
+
168
+ | 路由 | 说明 |
169
+ |---|---|
170
+ | `GET /` | 项目列表页面(搜索 + 快速跳转) |
171
+ | `GET /browse` | 项目的文件浏览器 SPA 壳 |
172
+ | `GET /preview?file=<path>` | 预览文件(Markdown、DrawIO、HTML、CSV 或代码) |
173
+ | `GET /api/projects` | 所有已发现项目的 JSON 列表 |
174
+ | `GET /api/files` | 项目中可预览文件的 JSON 列表 |
175
+ | `GET /api/file?path=<path>` | 原始文件内容 |
176
+ | `GET /api/render?file=<path>` | 渲染后的内容片段(JSON:`{ title, body, contentClass }`) |
177
+ | `GET /api/worktrees` | 项目的 Git 工作树 JSON 列表 |
178
+ | `GET /styles.css` | 样式表 |
179
+ | `WS /ws` | 实时刷新通知 |
180
+
181
+ ## 支持的语言
182
+
183
+ 代码渲染器识别 40+ 种文件扩展名和特殊文件名:
184
+
185
+ <details>
186
+ <summary>完整列表</summary>
187
+
188
+ **按扩展名**:`.ts`、`.tsx`、`.js`、`.jsx`、`.mjs`、`.cjs`、`.py`、`.rs`、`.go`、`.java`、`.kt`、`.c`、`.cpp`、`.h`、`.hpp`、`.cs`、`.rb`、`.php`、`.swift`、`.sh`、`.bash`、`.zsh`、`.fish`、`.sql`、`.css`、`.scss`、`.less`、`.json`、`.yaml`、`.yml`、`.toml`、`.xml`、`.graphql`、`.gql`、`.proto`、`.dockerfile`、`.lua`、`.r`、`.scala`、`.zig`、`.ex`、`.exs`、`.erl`、`.hs`、`.ml`、`.vue`、`.svelte`、`.tf`、`.ini`、`.conf`、`.env`、`.gitignore`、`.editorconfig`
189
+
190
+ **按文件名**:`Dockerfile`、`Makefile`、`Jenkinsfile`、`Vagrantfile`、`Gemfile`、`Rakefile`、`Justfile`
191
+
192
+ </details>
193
+
194
+ ## 环境要求
195
+
196
+ - [Bun](https://bun.sh) v1.0+
197
+ - OpenCode v1.3+(插件使用)
198
+
199
+ > **注意**:本包直接导出 `.ts`/`.tsx` 入口,需要 Bun 作为运行时。不兼容原生 Node.js。
200
+
201
+ ## 贡献
202
+
203
+ 欢迎提交 Issue 和 PR!本项目使用 Bun 进行开发:
204
+
205
+ ```bash
206
+ git clone https://github.com/mike652638/opencode-preview-plus.git
207
+ cd opencode-preview-plus
208
+ bun install
209
+ bun test
210
+ bun run dev
211
+ ```
212
+
213
+ ## 许可证
214
+
215
+ [MIT](LICENSE)
Binary file
Binary file
Binary file
@@ -3651,7 +3651,7 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
3651
3651
  initDrawio();
3652
3652
  scrollToHash();
3653
3653
  document.title = tab.title || "Preview";
3654
- updateSidebarActive(tab.file, tab.view || "file");
3654
+ updateSidebarActive(tab.file, tab.view || "file", { noScroll: true });
3655
3655
  renderTabBar();
3656
3656
  syncTabUrl();
3657
3657
  saveTabState();
@@ -3711,11 +3711,11 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
3711
3711
  }
3712
3712
  if (!preserveScroll) scrollToHash();
3713
3713
  document.title = tab.title;
3714
- updateSidebarActive(tab.file, tab.view || "file");
3714
+ updateSidebarActive(tab.file, tab.view || "file", { preserveScroll: preserveScroll });
3715
3715
  } else if (tabs[activeTabIndex] === tab) {
3716
3716
  content.style.opacity = "";
3717
3717
  document.title = tab.title;
3718
- updateSidebarActive(tab.file, tab.view || "file");
3718
+ updateSidebarActive(tab.file, tab.view || "file", { preserveScroll: preserveScroll });
3719
3719
  }
3720
3720
  renderTabBar();
3721
3721
  syncTabUrl();
@@ -4266,7 +4266,8 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
4266
4266
  })();
4267
4267
 
4268
4268
  // --- SPA router ---
4269
- function updateSidebarActive(file, view) {
4269
+ function updateSidebarActive(file, view, opts) {
4270
+ opts = opts || {};
4270
4271
  sidebar.querySelectorAll("a.file-link.active").forEach(function(a) { a.classList.remove("active"); });
4271
4272
  sidebar.querySelectorAll("a.change-link.active").forEach(function(a) { a.classList.remove("active"); });
4272
4273
  if (!file) return;
@@ -4300,7 +4301,13 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
4300
4301
  a.classList.add("active");
4301
4302
  var el = a.closest("details");
4302
4303
  while (el) { el.open = true; el = el.parentElement ? el.parentElement.closest("details") : null; }
4303
- a.scrollIntoView({ block: "nearest", behavior: "instant" });
4304
+ if (opts.noScroll) {
4305
+ sidebar.scrollTop = 0;
4306
+ } else if (opts.preserveScroll) {
4307
+ // Keep current scroll position - used by WebSocket refresh
4308
+ } else {
4309
+ a.scrollIntoView({ block: "center", behavior: "instant" });
4310
+ }
4304
4311
  }
4305
4312
  });
4306
4313
  }
@@ -4630,7 +4637,7 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
4630
4637
  scrollToHash();
4631
4638
  }
4632
4639
  renderTabBar();
4633
- updateSidebarActive(initialPath, initialView);
4640
+ updateSidebarActive(initialPath, initialView, { noScroll: true });
4634
4641
  saveTabState();
4635
4642
  } else if (saved && saved.tabs && saved.tabs.length > 0) {
4636
4643
  tabs = saved.tabs.map(function(t) { return { id: ++tabIdSeed, file: t.file, view: t.view || "file", title: t.title || t.file, cachedHtml: undefined, cachedClass: undefined, scrollTop: 0 }; });
@@ -4643,6 +4650,8 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
4643
4650
  renderTabBar();
4644
4651
  }
4645
4652
  refreshChangesList();
4653
+ // Reset sidebar scroll to top after initialization
4654
+ if (sidebar) sidebar.scrollTop = 0;
4646
4655
  })();
4647
4656
  })();`;
4648
4657
  }
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  isPreviewable,
5
5
  registerExternalPreviewFile,
6
6
  startServer
7
- } from "./chunk-NQ3FC3FR.js";
7
+ } from "./chunk-EIITP5VL.js";
8
8
 
9
9
  // src/index.ts
10
10
  import { stat } from "fs/promises";
package/dist/server.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  resolveExternalPreviewFile,
12
12
  startServer,
13
13
  stopServer
14
- } from "./chunk-NQ3FC3FR.js";
14
+ } from "./chunk-EIITP5VL.js";
15
15
  export {
16
16
  buildExternalPreviewUrl,
17
17
  clearExternalPreviewFilesForTest,
@@ -463,6 +463,7 @@ button:hover {
463
463
  gap: 0.4rem;
464
464
  border: 1px solid transparent;
465
465
  user-select: none;
466
+ overflow-anchor: none;
466
467
  }
467
468
 
468
469
  .folder-item details > summary::-webkit-details-marker {
@@ -681,6 +682,7 @@ button:hover {
681
682
  max-width: 50vw;
682
683
  height: 100vh;
683
684
  overflow-y: auto;
685
+ overflow-anchor: none;
684
686
  border-right: 1px solid var(--border);
685
687
  background: var(--card);
686
688
  flex-shrink: 0;
package/dist/tui.js CHANGED
@@ -3628,7 +3628,7 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
3628
3628
  initDrawio();
3629
3629
  scrollToHash();
3630
3630
  document.title = tab.title || "Preview";
3631
- updateSidebarActive(tab.file, tab.view || "file");
3631
+ updateSidebarActive(tab.file, tab.view || "file", { noScroll: true });
3632
3632
  renderTabBar();
3633
3633
  syncTabUrl();
3634
3634
  saveTabState();
@@ -3688,11 +3688,11 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
3688
3688
  }
3689
3689
  if (!preserveScroll) scrollToHash();
3690
3690
  document.title = tab.title;
3691
- updateSidebarActive(tab.file, tab.view || "file");
3691
+ updateSidebarActive(tab.file, tab.view || "file", { preserveScroll: preserveScroll });
3692
3692
  } else if (tabs[activeTabIndex] === tab) {
3693
3693
  content.style.opacity = "";
3694
3694
  document.title = tab.title;
3695
- updateSidebarActive(tab.file, tab.view || "file");
3695
+ updateSidebarActive(tab.file, tab.view || "file", { preserveScroll: preserveScroll });
3696
3696
  }
3697
3697
  renderTabBar();
3698
3698
  syncTabUrl();
@@ -4243,7 +4243,8 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
4243
4243
  })();
4244
4244
 
4245
4245
  // --- SPA router ---
4246
- function updateSidebarActive(file, view) {
4246
+ function updateSidebarActive(file, view, opts) {
4247
+ opts = opts || {};
4247
4248
  sidebar.querySelectorAll("a.file-link.active").forEach(function(a) { a.classList.remove("active"); });
4248
4249
  sidebar.querySelectorAll("a.change-link.active").forEach(function(a) { a.classList.remove("active"); });
4249
4250
  if (!file) return;
@@ -4277,7 +4278,13 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
4277
4278
  a.classList.add("active");
4278
4279
  var el = a.closest("details");
4279
4280
  while (el) { el.open = true; el = el.parentElement ? el.parentElement.closest("details") : null; }
4280
- a.scrollIntoView({ block: "nearest", behavior: "instant" });
4281
+ if (opts.noScroll) {
4282
+ sidebar.scrollTop = 0;
4283
+ } else if (opts.preserveScroll) {
4284
+ // Keep current scroll position - used by WebSocket refresh
4285
+ } else {
4286
+ a.scrollIntoView({ block: "center", behavior: "instant" });
4287
+ }
4281
4288
  }
4282
4289
  });
4283
4290
  }
@@ -4607,7 +4614,7 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
4607
4614
  scrollToHash();
4608
4615
  }
4609
4616
  renderTabBar();
4610
- updateSidebarActive(initialPath, initialView);
4617
+ updateSidebarActive(initialPath, initialView, { noScroll: true });
4611
4618
  saveTabState();
4612
4619
  } else if (saved && saved.tabs && saved.tabs.length > 0) {
4613
4620
  tabs = saved.tabs.map(function(t) { return { id: ++tabIdSeed, file: t.file, view: t.view || "file", title: t.title || t.file, cachedHtml: undefined, cachedClass: undefined, scrollTop: 0 }; });
@@ -4620,6 +4627,8 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
4620
4627
  renderTabBar();
4621
4628
  }
4622
4629
  refreshChangesList();
4630
+ // Reset sidebar scroll to top after initialization
4631
+ if (sidebar) sidebar.scrollTop = 0;
4623
4632
  })();
4624
4633
  })();`;
4625
4634
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-preview-plus",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "description": "OpenCode plugin for Markdown and DrawIO preview with live reload",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -31,6 +31,8 @@
31
31
  "files": [
32
32
  "dist",
33
33
  "README.md",
34
+ "README.en.md",
35
+ "assets",
34
36
  "LICENSE"
35
37
  ],
36
38
  "scripts": {
@@ -72,6 +74,7 @@
72
74
  "@types/bun": "latest",
73
75
  "@types/ws": "^8",
74
76
  "baseline-browser-mapping": "^2.10.12",
77
+ "playwright": "^1.61.1",
75
78
  "solid-js": "^1.9.0",
76
79
  "tsup": "^8.5.1"
77
80
  }