opencode-preview-plus 0.13.0 → 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 +215 -0
- package/README.md +215 -199
- package/assets/preview-1.png +0 -0
- package/assets/preview-2.png +0 -0
- package/assets/preview-3.png +0 -0
- package/dist/{chunk-ZHJFGWJ2.js → chunk-EIITP5VL.js} +33 -9
- package/dist/index.js +1 -1
- package/dist/server.js +1 -1
- package/dist/templates/styles.css +2 -0
- package/dist/tui.js +33 -9
- package/package.json +4 -1
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
|
+

|
|
24
|
+
|
|
25
|
+
**AI-powered preview tool invocation**
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
**Browser preview UI (file tree + TOC + Markdown rendering)**
|
|
30
|
+
|
|
31
|
+

|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
|
50
|
-
|
|
51
|
-
| **
|
|
52
|
-
| **
|
|
53
|
-
| **
|
|
54
|
-
| **
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
|
61
|
-
|
|
62
|
-
| **
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
|
67
|
-
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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> 提供即时文件预览 — Markdown、DrawIO、HTML、CSV 及 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
|
+

|
|
24
|
+
|
|
25
|
+
**AI 智能调用预览工具**
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
**浏览器预览界面(文件树 + 目录大纲 + Markdown 渲染)**
|
|
30
|
+
|
|
31
|
+

|
|
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
|
|
@@ -2844,7 +2844,12 @@ function clearExternalPreviewFilesForTest() {
|
|
|
2844
2844
|
}
|
|
2845
2845
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "__pycache__", ".venv", "venv"]);
|
|
2846
2846
|
async function collectPreviewFiles(directory, base = directory) {
|
|
2847
|
-
|
|
2847
|
+
let entries;
|
|
2848
|
+
try {
|
|
2849
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
2850
|
+
} catch {
|
|
2851
|
+
return [];
|
|
2852
|
+
}
|
|
2848
2853
|
const files = await Promise.all(
|
|
2849
2854
|
entries.map(async (entry) => {
|
|
2850
2855
|
const absolutePath = path2.join(directory, entry.name);
|
|
@@ -2863,12 +2868,22 @@ async function collectPreviewFiles(directory, base = directory) {
|
|
|
2863
2868
|
return files.flat().sort((a, b) => a.localeCompare(b));
|
|
2864
2869
|
}
|
|
2865
2870
|
async function collectEmptyDirectories(directory, base = directory) {
|
|
2866
|
-
|
|
2871
|
+
let entries;
|
|
2872
|
+
try {
|
|
2873
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
2874
|
+
} catch {
|
|
2875
|
+
return [];
|
|
2876
|
+
}
|
|
2867
2877
|
const childDirectories = entries.filter((entry) => entry.isDirectory() && !IGNORED_DIRS.has(entry.name));
|
|
2868
2878
|
const nested = await Promise.all(
|
|
2869
2879
|
childDirectories.map(async (entry) => {
|
|
2870
2880
|
const absolutePath = path2.join(directory, entry.name);
|
|
2871
|
-
|
|
2881
|
+
let childEntries;
|
|
2882
|
+
try {
|
|
2883
|
+
childEntries = await readdir(absolutePath, { withFileTypes: true });
|
|
2884
|
+
} catch {
|
|
2885
|
+
return [];
|
|
2886
|
+
}
|
|
2872
2887
|
const childResults = await collectEmptyDirectories(absolutePath, base);
|
|
2873
2888
|
if (childEntries.length === 0) {
|
|
2874
2889
|
return [path2.relative(base, absolutePath).split(path2.sep).join("/"), ...childResults];
|
|
@@ -3636,7 +3651,7 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
|
|
|
3636
3651
|
initDrawio();
|
|
3637
3652
|
scrollToHash();
|
|
3638
3653
|
document.title = tab.title || "Preview";
|
|
3639
|
-
updateSidebarActive(tab.file, tab.view || "file");
|
|
3654
|
+
updateSidebarActive(tab.file, tab.view || "file", { noScroll: true });
|
|
3640
3655
|
renderTabBar();
|
|
3641
3656
|
syncTabUrl();
|
|
3642
3657
|
saveTabState();
|
|
@@ -3696,11 +3711,11 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
|
|
|
3696
3711
|
}
|
|
3697
3712
|
if (!preserveScroll) scrollToHash();
|
|
3698
3713
|
document.title = tab.title;
|
|
3699
|
-
updateSidebarActive(tab.file, tab.view || "file");
|
|
3714
|
+
updateSidebarActive(tab.file, tab.view || "file", { preserveScroll: preserveScroll });
|
|
3700
3715
|
} else if (tabs[activeTabIndex] === tab) {
|
|
3701
3716
|
content.style.opacity = "";
|
|
3702
3717
|
document.title = tab.title;
|
|
3703
|
-
updateSidebarActive(tab.file, tab.view || "file");
|
|
3718
|
+
updateSidebarActive(tab.file, tab.view || "file", { preserveScroll: preserveScroll });
|
|
3704
3719
|
}
|
|
3705
3720
|
renderTabBar();
|
|
3706
3721
|
syncTabUrl();
|
|
@@ -4251,7 +4266,8 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
|
|
|
4251
4266
|
})();
|
|
4252
4267
|
|
|
4253
4268
|
// --- SPA router ---
|
|
4254
|
-
function updateSidebarActive(file, view) {
|
|
4269
|
+
function updateSidebarActive(file, view, opts) {
|
|
4270
|
+
opts = opts || {};
|
|
4255
4271
|
sidebar.querySelectorAll("a.file-link.active").forEach(function(a) { a.classList.remove("active"); });
|
|
4256
4272
|
sidebar.querySelectorAll("a.change-link.active").forEach(function(a) { a.classList.remove("active"); });
|
|
4257
4273
|
if (!file) return;
|
|
@@ -4285,7 +4301,13 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
|
|
|
4285
4301
|
a.classList.add("active");
|
|
4286
4302
|
var el = a.closest("details");
|
|
4287
4303
|
while (el) { el.open = true; el = el.parentElement ? el.parentElement.closest("details") : null; }
|
|
4288
|
-
|
|
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
|
+
}
|
|
4289
4311
|
}
|
|
4290
4312
|
});
|
|
4291
4313
|
}
|
|
@@ -4615,7 +4637,7 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
|
|
|
4615
4637
|
scrollToHash();
|
|
4616
4638
|
}
|
|
4617
4639
|
renderTabBar();
|
|
4618
|
-
updateSidebarActive(initialPath, initialView);
|
|
4640
|
+
updateSidebarActive(initialPath, initialView, { noScroll: true });
|
|
4619
4641
|
saveTabState();
|
|
4620
4642
|
} else if (saved && saved.tabs && saved.tabs.length > 0) {
|
|
4621
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 }; });
|
|
@@ -4628,6 +4650,8 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
|
|
|
4628
4650
|
renderTabBar();
|
|
4629
4651
|
}
|
|
4630
4652
|
refreshChangesList();
|
|
4653
|
+
// Reset sidebar scroll to top after initialization
|
|
4654
|
+
if (sidebar) sidebar.scrollTop = 0;
|
|
4631
4655
|
})();
|
|
4632
4656
|
})();`;
|
|
4633
4657
|
}
|
package/dist/index.js
CHANGED
package/dist/server.js
CHANGED
|
@@ -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
|
@@ -2827,7 +2827,12 @@ function resolveExternalPreviewFile(token) {
|
|
|
2827
2827
|
}
|
|
2828
2828
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "__pycache__", ".venv", "venv"]);
|
|
2829
2829
|
async function collectPreviewFiles(directory, base = directory) {
|
|
2830
|
-
|
|
2830
|
+
let entries;
|
|
2831
|
+
try {
|
|
2832
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
2833
|
+
} catch {
|
|
2834
|
+
return [];
|
|
2835
|
+
}
|
|
2831
2836
|
const files = await Promise.all(
|
|
2832
2837
|
entries.map(async (entry) => {
|
|
2833
2838
|
const absolutePath = path2.join(directory, entry.name);
|
|
@@ -2846,12 +2851,22 @@ async function collectPreviewFiles(directory, base = directory) {
|
|
|
2846
2851
|
return files.flat().sort((a, b) => a.localeCompare(b));
|
|
2847
2852
|
}
|
|
2848
2853
|
async function collectEmptyDirectories(directory, base = directory) {
|
|
2849
|
-
|
|
2854
|
+
let entries;
|
|
2855
|
+
try {
|
|
2856
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
2857
|
+
} catch {
|
|
2858
|
+
return [];
|
|
2859
|
+
}
|
|
2850
2860
|
const childDirectories = entries.filter((entry) => entry.isDirectory() && !IGNORED_DIRS.has(entry.name));
|
|
2851
2861
|
const nested = await Promise.all(
|
|
2852
2862
|
childDirectories.map(async (entry) => {
|
|
2853
2863
|
const absolutePath = path2.join(directory, entry.name);
|
|
2854
|
-
|
|
2864
|
+
let childEntries;
|
|
2865
|
+
try {
|
|
2866
|
+
childEntries = await readdir(absolutePath, { withFileTypes: true });
|
|
2867
|
+
} catch {
|
|
2868
|
+
return [];
|
|
2869
|
+
}
|
|
2855
2870
|
const childResults = await collectEmptyDirectories(absolutePath, base);
|
|
2856
2871
|
if (childEntries.length === 0) {
|
|
2857
2872
|
return [path2.relative(base, absolutePath).split(path2.sep).join("/"), ...childResults];
|
|
@@ -3613,7 +3628,7 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
|
|
|
3613
3628
|
initDrawio();
|
|
3614
3629
|
scrollToHash();
|
|
3615
3630
|
document.title = tab.title || "Preview";
|
|
3616
|
-
updateSidebarActive(tab.file, tab.view || "file");
|
|
3631
|
+
updateSidebarActive(tab.file, tab.view || "file", { noScroll: true });
|
|
3617
3632
|
renderTabBar();
|
|
3618
3633
|
syncTabUrl();
|
|
3619
3634
|
saveTabState();
|
|
@@ -3673,11 +3688,11 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
|
|
|
3673
3688
|
}
|
|
3674
3689
|
if (!preserveScroll) scrollToHash();
|
|
3675
3690
|
document.title = tab.title;
|
|
3676
|
-
updateSidebarActive(tab.file, tab.view || "file");
|
|
3691
|
+
updateSidebarActive(tab.file, tab.view || "file", { preserveScroll: preserveScroll });
|
|
3677
3692
|
} else if (tabs[activeTabIndex] === tab) {
|
|
3678
3693
|
content.style.opacity = "";
|
|
3679
3694
|
document.title = tab.title;
|
|
3680
|
-
updateSidebarActive(tab.file, tab.view || "file");
|
|
3695
|
+
updateSidebarActive(tab.file, tab.view || "file", { preserveScroll: preserveScroll });
|
|
3681
3696
|
}
|
|
3682
3697
|
renderTabBar();
|
|
3683
3698
|
syncTabUrl();
|
|
@@ -4228,7 +4243,8 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
|
|
|
4228
4243
|
})();
|
|
4229
4244
|
|
|
4230
4245
|
// --- SPA router ---
|
|
4231
|
-
function updateSidebarActive(file, view) {
|
|
4246
|
+
function updateSidebarActive(file, view, opts) {
|
|
4247
|
+
opts = opts || {};
|
|
4232
4248
|
sidebar.querySelectorAll("a.file-link.active").forEach(function(a) { a.classList.remove("active"); });
|
|
4233
4249
|
sidebar.querySelectorAll("a.change-link.active").forEach(function(a) { a.classList.remove("active"); });
|
|
4234
4250
|
if (!file) return;
|
|
@@ -4262,7 +4278,13 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
|
|
|
4262
4278
|
a.classList.add("active");
|
|
4263
4279
|
var el = a.closest("details");
|
|
4264
4280
|
while (el) { el.open = true; el = el.parentElement ? el.parentElement.closest("details") : null; }
|
|
4265
|
-
|
|
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
|
+
}
|
|
4266
4288
|
}
|
|
4267
4289
|
});
|
|
4268
4290
|
}
|
|
@@ -4592,7 +4614,7 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
|
|
|
4592
4614
|
scrollToHash();
|
|
4593
4615
|
}
|
|
4594
4616
|
renderTabBar();
|
|
4595
|
-
updateSidebarActive(initialPath, initialView);
|
|
4617
|
+
updateSidebarActive(initialPath, initialView, { noScroll: true });
|
|
4596
4618
|
saveTabState();
|
|
4597
4619
|
} else if (saved && saved.tabs && saved.tabs.length > 0) {
|
|
4598
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 }; });
|
|
@@ -4605,6 +4627,8 @@ function shellScript(projectId, worktreeParams, rootDir, externalToken) {
|
|
|
4605
4627
|
renderTabBar();
|
|
4606
4628
|
}
|
|
4607
4629
|
refreshChangesList();
|
|
4630
|
+
// Reset sidebar scroll to top after initialization
|
|
4631
|
+
if (sidebar) sidebar.scrollTop = 0;
|
|
4608
4632
|
})();
|
|
4609
4633
|
})();`;
|
|
4610
4634
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-preview-plus",
|
|
3
|
-
"version": "0.
|
|
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
|
}
|