@incremark/devtools 0.0.1
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/LICENSE +22 -0
- package/README.md +101 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.js +723 -0
- package/package.json +31 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 wangyishuai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# @incremark/devtools
|
|
2
|
+
|
|
3
|
+
Incremark 的开发者工具,框架无关。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- 🔍 **实时状态** - 查看解析状态、块列表、AST
|
|
8
|
+
- 📊 **时间线** - 记录每次 append 操作
|
|
9
|
+
- 🎨 **主题** - 支持 dark/light 主题
|
|
10
|
+
- 📦 **框架无关** - 可在 Vue、React 或原生 JS 中使用
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @incremark/devtools
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 使用
|
|
19
|
+
|
|
20
|
+
### 与 Vue 配合
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { useIncremark, useDevTools } from '@incremark/vue'
|
|
24
|
+
|
|
25
|
+
const incremark = useIncremark()
|
|
26
|
+
useDevTools(incremark)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 与 React 配合
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { useIncremark, useDevTools } from '@incremark/react'
|
|
33
|
+
|
|
34
|
+
function App() {
|
|
35
|
+
const incremark = useIncremark()
|
|
36
|
+
useDevTools(incremark)
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 独立使用
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { createIncremarkParser } from '@incremark/core'
|
|
44
|
+
import { mountDevTools } from '@incremark/devtools'
|
|
45
|
+
|
|
46
|
+
const parser = createIncremarkParser()
|
|
47
|
+
parser.setOnChange(mountDevTools())
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API
|
|
51
|
+
|
|
52
|
+
### mountDevTools(options?, target?)
|
|
53
|
+
|
|
54
|
+
创建并挂载 DevTools,返回 onChange 回调。
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const callback = mountDevTools({
|
|
58
|
+
open: false,
|
|
59
|
+
position: 'bottom-right',
|
|
60
|
+
theme: 'dark'
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
parser.setOnChange(callback)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### IncremarkDevTools
|
|
67
|
+
|
|
68
|
+
DevTools 类,提供更细粒度控制。
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const devtools = new IncremarkDevTools(options)
|
|
72
|
+
devtools.mount()
|
|
73
|
+
devtools.update(parserState)
|
|
74
|
+
devtools.unmount()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 配置选项
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
interface DevToolsOptions {
|
|
81
|
+
open?: boolean // 初始是否打开
|
|
82
|
+
position?: Position // 位置
|
|
83
|
+
theme?: 'dark' | 'light' // 主题
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type Position = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 功能面板
|
|
90
|
+
|
|
91
|
+
| 面板 | 功能 |
|
|
92
|
+
|------|------|
|
|
93
|
+
| Overview | 显示字符数、块数量等统计 |
|
|
94
|
+
| Blocks | 查看所有解析出的块 |
|
|
95
|
+
| AST | JSON 格式的完整 AST |
|
|
96
|
+
| Timeline | append 操作历史 |
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
|
101
|
+
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { ParsedBlock, Root } from '@incremark/core';
|
|
2
|
+
|
|
3
|
+
interface DevToolsState {
|
|
4
|
+
/** 所有块 */
|
|
5
|
+
blocks: Array<ParsedBlock & {
|
|
6
|
+
stableId: string;
|
|
7
|
+
}>;
|
|
8
|
+
/** 已完成的块 */
|
|
9
|
+
completedBlocks: ParsedBlock[];
|
|
10
|
+
/** 待处理的块 */
|
|
11
|
+
pendingBlocks: ParsedBlock[];
|
|
12
|
+
/** Markdown 字符串 */
|
|
13
|
+
markdown: string;
|
|
14
|
+
/** AST */
|
|
15
|
+
ast: Root;
|
|
16
|
+
/** 是否加载中 */
|
|
17
|
+
isLoading: boolean;
|
|
18
|
+
}
|
|
19
|
+
interface AppendRecord {
|
|
20
|
+
timestamp: number;
|
|
21
|
+
chunk: string;
|
|
22
|
+
completedCount: number;
|
|
23
|
+
pendingCount: number;
|
|
24
|
+
}
|
|
25
|
+
interface DevToolsOptions {
|
|
26
|
+
/** 初始是否打开 */
|
|
27
|
+
open?: boolean;
|
|
28
|
+
/** 位置 */
|
|
29
|
+
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
30
|
+
/** 主题 */
|
|
31
|
+
theme?: 'dark' | 'light';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
declare class IncremarkDevTools {
|
|
35
|
+
private container;
|
|
36
|
+
private state;
|
|
37
|
+
private isOpen;
|
|
38
|
+
private activeTab;
|
|
39
|
+
private selectedBlockId;
|
|
40
|
+
private appendHistory;
|
|
41
|
+
private lastMarkdownLength;
|
|
42
|
+
private options;
|
|
43
|
+
private styleElement;
|
|
44
|
+
constructor(options?: DevToolsOptions);
|
|
45
|
+
/**
|
|
46
|
+
* 挂载 DevTools 到 DOM
|
|
47
|
+
*/
|
|
48
|
+
mount(target?: HTMLElement | string): this;
|
|
49
|
+
/**
|
|
50
|
+
* 卸载 DevTools
|
|
51
|
+
*/
|
|
52
|
+
unmount(): this;
|
|
53
|
+
/**
|
|
54
|
+
* 更新状态
|
|
55
|
+
*/
|
|
56
|
+
update(state: DevToolsState): this;
|
|
57
|
+
/**
|
|
58
|
+
* 重置状态
|
|
59
|
+
*/
|
|
60
|
+
reset(): this;
|
|
61
|
+
/**
|
|
62
|
+
* 打开面板
|
|
63
|
+
*/
|
|
64
|
+
open(): this;
|
|
65
|
+
/**
|
|
66
|
+
* 关闭面板
|
|
67
|
+
*/
|
|
68
|
+
close(): this;
|
|
69
|
+
/**
|
|
70
|
+
* 切换面板
|
|
71
|
+
*/
|
|
72
|
+
toggle(): this;
|
|
73
|
+
private render;
|
|
74
|
+
private renderTabContent;
|
|
75
|
+
private renderOverview;
|
|
76
|
+
private renderBlocks;
|
|
77
|
+
private renderAst;
|
|
78
|
+
private renderTimeline;
|
|
79
|
+
private bindEvents;
|
|
80
|
+
private truncate;
|
|
81
|
+
private formatTime;
|
|
82
|
+
private escapeHtml;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 创建 DevTools 实例
|
|
86
|
+
*/
|
|
87
|
+
declare function createDevTools(options?: DevToolsOptions): IncremarkDevTools;
|
|
88
|
+
/**
|
|
89
|
+
* 一行代码挂载 DevTools
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* import { createIncremarkParser } from '@incremark/core'
|
|
94
|
+
* import { mountDevTools } from '@incremark/devtools'
|
|
95
|
+
*
|
|
96
|
+
* const parser = createIncremarkParser({
|
|
97
|
+
* onChange: mountDevTools() // 就这一行!
|
|
98
|
+
* })
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* // 或者自定义选项
|
|
104
|
+
* const parser = createIncremarkParser({
|
|
105
|
+
* onChange: mountDevTools({ position: 'bottom-left', theme: 'light' })
|
|
106
|
+
* })
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
declare function mountDevTools(options?: DevToolsOptions): (state: {
|
|
110
|
+
completedBlocks: any[];
|
|
111
|
+
pendingBlocks: any[];
|
|
112
|
+
markdown: string;
|
|
113
|
+
ast: any;
|
|
114
|
+
}) => void;
|
|
115
|
+
|
|
116
|
+
export { type AppendRecord, type DevToolsOptions, type DevToolsState, IncremarkDevTools, createDevTools, mountDevTools };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
// src/styles.ts
|
|
2
|
+
var styles = `
|
|
3
|
+
.incremark-devtools {
|
|
4
|
+
position: fixed;
|
|
5
|
+
z-index: 99999;
|
|
6
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;
|
|
7
|
+
font-size: 13px;
|
|
8
|
+
line-height: 1.5;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.incremark-devtools.bottom-right { bottom: 20px; right: 20px; }
|
|
12
|
+
.incremark-devtools.bottom-left { bottom: 20px; left: 20px; }
|
|
13
|
+
.incremark-devtools.top-right { top: 20px; right: 20px; }
|
|
14
|
+
.incremark-devtools.top-left { top: 20px; left: 20px; }
|
|
15
|
+
|
|
16
|
+
.incremark-devtools * {
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.devtools-trigger {
|
|
23
|
+
width: 48px;
|
|
24
|
+
height: 48px;
|
|
25
|
+
border-radius: 50%;
|
|
26
|
+
border: none;
|
|
27
|
+
background: #1e1e1e;
|
|
28
|
+
color: white;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
34
|
+
position: relative;
|
|
35
|
+
font-size: 20px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.devtools-trigger:hover {
|
|
39
|
+
background: #2d2d2d;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.loading-dot {
|
|
43
|
+
position: absolute;
|
|
44
|
+
top: 8px;
|
|
45
|
+
right: 8px;
|
|
46
|
+
width: 8px;
|
|
47
|
+
height: 8px;
|
|
48
|
+
background: #22c55e;
|
|
49
|
+
border-radius: 50%;
|
|
50
|
+
animation: pulse 1s infinite;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@keyframes pulse {
|
|
54
|
+
0%, 100% { opacity: 1; }
|
|
55
|
+
50% { opacity: 0.5; }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.devtools-panel {
|
|
59
|
+
position: absolute;
|
|
60
|
+
width: 420px;
|
|
61
|
+
max-height: 500px;
|
|
62
|
+
background: #1e1e1e;
|
|
63
|
+
border-radius: 12px;
|
|
64
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
display: none;
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.devtools-panel.open {
|
|
71
|
+
display: flex;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.bottom-right .devtools-panel,
|
|
75
|
+
.bottom-left .devtools-panel {
|
|
76
|
+
bottom: 60px;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.top-right .devtools-panel,
|
|
80
|
+
.top-left .devtools-panel {
|
|
81
|
+
top: 60px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.bottom-right .devtools-panel,
|
|
85
|
+
.top-right .devtools-panel {
|
|
86
|
+
right: 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.bottom-left .devtools-panel,
|
|
90
|
+
.top-left .devtools-panel {
|
|
91
|
+
left: 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.devtools-header {
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
padding: 12px 16px;
|
|
98
|
+
background: #252525;
|
|
99
|
+
border-bottom: 1px solid #333;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.devtools-header h3 {
|
|
103
|
+
margin: 0;
|
|
104
|
+
font-size: 14px;
|
|
105
|
+
font-weight: 600;
|
|
106
|
+
color: #fff;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.devtools-tabs {
|
|
110
|
+
display: flex;
|
|
111
|
+
gap: 4px;
|
|
112
|
+
margin-left: auto;
|
|
113
|
+
margin-right: 12px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.devtools-tabs button {
|
|
117
|
+
padding: 4px 10px;
|
|
118
|
+
border: none;
|
|
119
|
+
background: transparent;
|
|
120
|
+
color: #888;
|
|
121
|
+
cursor: pointer;
|
|
122
|
+
border-radius: 4px;
|
|
123
|
+
font-size: 12px;
|
|
124
|
+
text-transform: capitalize;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.devtools-tabs button.active {
|
|
128
|
+
background: #3b82f6;
|
|
129
|
+
color: white;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.close-btn {
|
|
133
|
+
width: 24px;
|
|
134
|
+
height: 24px;
|
|
135
|
+
border: none;
|
|
136
|
+
background: transparent;
|
|
137
|
+
color: #888;
|
|
138
|
+
cursor: pointer;
|
|
139
|
+
font-size: 18px;
|
|
140
|
+
line-height: 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.close-btn:hover {
|
|
144
|
+
color: #fff;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.devtools-content {
|
|
148
|
+
flex: 1;
|
|
149
|
+
overflow-y: auto;
|
|
150
|
+
padding: 16px;
|
|
151
|
+
color: #e0e0e0;
|
|
152
|
+
min-height: 200px;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.stats-grid {
|
|
156
|
+
display: grid;
|
|
157
|
+
grid-template-columns: repeat(4, 1fr);
|
|
158
|
+
gap: 12px;
|
|
159
|
+
margin-bottom: 20px;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.stat-card {
|
|
163
|
+
background: #2a2a2a;
|
|
164
|
+
padding: 12px;
|
|
165
|
+
border-radius: 8px;
|
|
166
|
+
text-align: center;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.stat-value {
|
|
170
|
+
font-size: 24px;
|
|
171
|
+
font-weight: 700;
|
|
172
|
+
color: #fff;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.stat-label {
|
|
176
|
+
font-size: 11px;
|
|
177
|
+
color: #888;
|
|
178
|
+
margin-top: 4px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.stat-card.completed .stat-value { color: #22c55e; }
|
|
182
|
+
.stat-card.pending .stat-value { color: #a855f7; }
|
|
183
|
+
|
|
184
|
+
.section {
|
|
185
|
+
margin-bottom: 20px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.section h4 {
|
|
189
|
+
margin: 0 0 12px;
|
|
190
|
+
font-size: 12px;
|
|
191
|
+
color: #888;
|
|
192
|
+
text-transform: uppercase;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.type-bars {
|
|
196
|
+
display: flex;
|
|
197
|
+
flex-direction: column;
|
|
198
|
+
gap: 6px;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.type-bar {
|
|
202
|
+
display: flex;
|
|
203
|
+
align-items: center;
|
|
204
|
+
gap: 8px;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.type-color {
|
|
208
|
+
width: 12px;
|
|
209
|
+
height: 12px;
|
|
210
|
+
border-radius: 3px;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.type-name { flex: 1; }
|
|
214
|
+
.type-count { color: #888; }
|
|
215
|
+
|
|
216
|
+
.status-indicator {
|
|
217
|
+
padding: 8px 12px;
|
|
218
|
+
background: #2a2a2a;
|
|
219
|
+
border-radius: 6px;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.status-indicator.loading {
|
|
223
|
+
background: #22c55e20;
|
|
224
|
+
color: #22c55e;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.blocks-list {
|
|
228
|
+
max-height: 200px;
|
|
229
|
+
overflow-y: auto;
|
|
230
|
+
margin-bottom: 16px;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.block-item {
|
|
234
|
+
display: flex;
|
|
235
|
+
align-items: center;
|
|
236
|
+
gap: 8px;
|
|
237
|
+
padding: 8px;
|
|
238
|
+
cursor: pointer;
|
|
239
|
+
border-radius: 6px;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.block-item:hover { background: #2a2a2a; }
|
|
243
|
+
.block-item.selected { background: #3b82f620; }
|
|
244
|
+
|
|
245
|
+
.block-status {
|
|
246
|
+
width: 8px;
|
|
247
|
+
height: 8px;
|
|
248
|
+
border-radius: 50%;
|
|
249
|
+
background: #888;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.block-status.completed { background: #22c55e; }
|
|
253
|
+
.block-status.pending { background: #a855f7; }
|
|
254
|
+
|
|
255
|
+
.block-type {
|
|
256
|
+
font-weight: 600;
|
|
257
|
+
min-width: 80px;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.block-preview {
|
|
261
|
+
color: #888;
|
|
262
|
+
font-size: 12px;
|
|
263
|
+
white-space: nowrap;
|
|
264
|
+
overflow: hidden;
|
|
265
|
+
text-overflow: ellipsis;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.block-detail {
|
|
269
|
+
background: #2a2a2a;
|
|
270
|
+
padding: 12px;
|
|
271
|
+
border-radius: 8px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.block-detail h4,
|
|
275
|
+
.block-detail h5 {
|
|
276
|
+
margin: 0 0 8px;
|
|
277
|
+
font-size: 12px;
|
|
278
|
+
color: #888;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.block-detail h5 { margin-top: 12px; }
|
|
282
|
+
|
|
283
|
+
.detail-row {
|
|
284
|
+
display: flex;
|
|
285
|
+
gap: 8px;
|
|
286
|
+
margin-bottom: 4px;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.detail-row .label {
|
|
290
|
+
color: #888;
|
|
291
|
+
min-width: 50px;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.detail-row .value.completed { color: #22c55e; }
|
|
295
|
+
.detail-row .value.pending { color: #a855f7; }
|
|
296
|
+
|
|
297
|
+
.raw-text,
|
|
298
|
+
.ast-json,
|
|
299
|
+
.ast-tree {
|
|
300
|
+
background: #1a1a1a;
|
|
301
|
+
padding: 12px;
|
|
302
|
+
border-radius: 6px;
|
|
303
|
+
overflow-x: auto;
|
|
304
|
+
font-size: 11px;
|
|
305
|
+
white-space: pre-wrap;
|
|
306
|
+
word-break: break-all;
|
|
307
|
+
max-height: 150px;
|
|
308
|
+
font-family: monospace;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.timeline-header {
|
|
312
|
+
display: flex;
|
|
313
|
+
justify-content: space-between;
|
|
314
|
+
align-items: center;
|
|
315
|
+
margin-bottom: 12px;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.timeline-header button {
|
|
319
|
+
padding: 4px 8px;
|
|
320
|
+
border: none;
|
|
321
|
+
background: #333;
|
|
322
|
+
color: #888;
|
|
323
|
+
border-radius: 4px;
|
|
324
|
+
cursor: pointer;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.timeline-list {
|
|
328
|
+
max-height: 300px;
|
|
329
|
+
overflow-y: auto;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.timeline-item {
|
|
333
|
+
display: flex;
|
|
334
|
+
gap: 12px;
|
|
335
|
+
padding: 8px 0;
|
|
336
|
+
border-bottom: 1px solid #333;
|
|
337
|
+
font-size: 12px;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.timeline-item .time {
|
|
341
|
+
color: #888;
|
|
342
|
+
min-width: 70px;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.timeline-item .chunk {
|
|
346
|
+
flex: 1;
|
|
347
|
+
color: #22c55e;
|
|
348
|
+
font-family: monospace;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.timeline-item .stats { color: #888; }
|
|
352
|
+
|
|
353
|
+
/* Light theme */
|
|
354
|
+
.incremark-devtools.light .devtools-trigger {
|
|
355
|
+
background: #fff;
|
|
356
|
+
color: #333;
|
|
357
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.incremark-devtools.light .devtools-panel {
|
|
361
|
+
background: #fff;
|
|
362
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.incremark-devtools.light .devtools-header {
|
|
366
|
+
background: #f5f5f5;
|
|
367
|
+
border-bottom: 1px solid #e0e0e0;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.incremark-devtools.light .devtools-header h3 { color: #333; }
|
|
371
|
+
.incremark-devtools.light .devtools-content { color: #333; }
|
|
372
|
+
.incremark-devtools.light .stat-card { background: #f5f5f5; }
|
|
373
|
+
.incremark-devtools.light .stat-value { color: #333; }
|
|
374
|
+
`;
|
|
375
|
+
|
|
376
|
+
// src/devtools.ts
|
|
377
|
+
var NODE_TYPE_COLORS = {
|
|
378
|
+
heading: "#3b82f6",
|
|
379
|
+
paragraph: "#22c55e",
|
|
380
|
+
code: "#f59e0b",
|
|
381
|
+
list: "#8b5cf6",
|
|
382
|
+
table: "#ec4899",
|
|
383
|
+
blockquote: "#06b6d4",
|
|
384
|
+
thematicBreak: "#6b7280"
|
|
385
|
+
};
|
|
386
|
+
var IncremarkDevTools = class {
|
|
387
|
+
container = null;
|
|
388
|
+
state = null;
|
|
389
|
+
isOpen = false;
|
|
390
|
+
activeTab = "overview";
|
|
391
|
+
selectedBlockId = null;
|
|
392
|
+
appendHistory = [];
|
|
393
|
+
lastMarkdownLength = 0;
|
|
394
|
+
options;
|
|
395
|
+
styleElement = null;
|
|
396
|
+
constructor(options = {}) {
|
|
397
|
+
this.options = {
|
|
398
|
+
open: options.open ?? false,
|
|
399
|
+
position: options.position ?? "bottom-right",
|
|
400
|
+
theme: options.theme ?? "dark"
|
|
401
|
+
};
|
|
402
|
+
this.isOpen = this.options.open;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* 挂载 DevTools 到 DOM
|
|
406
|
+
*/
|
|
407
|
+
mount(target = "body") {
|
|
408
|
+
const parent = typeof target === "string" ? document.querySelector(target) : target;
|
|
409
|
+
if (!parent) {
|
|
410
|
+
console.warn("[IncremarkDevTools] Mount target not found");
|
|
411
|
+
return this;
|
|
412
|
+
}
|
|
413
|
+
if (!this.styleElement) {
|
|
414
|
+
this.styleElement = document.createElement("style");
|
|
415
|
+
this.styleElement.textContent = styles;
|
|
416
|
+
document.head.appendChild(this.styleElement);
|
|
417
|
+
}
|
|
418
|
+
this.container = document.createElement("div");
|
|
419
|
+
this.container.className = `incremark-devtools ${this.options.position} ${this.options.theme}`;
|
|
420
|
+
this.render();
|
|
421
|
+
parent.appendChild(this.container);
|
|
422
|
+
return this;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* 卸载 DevTools
|
|
426
|
+
*/
|
|
427
|
+
unmount() {
|
|
428
|
+
if (this.container) {
|
|
429
|
+
this.container.remove();
|
|
430
|
+
this.container = null;
|
|
431
|
+
}
|
|
432
|
+
if (this.styleElement) {
|
|
433
|
+
this.styleElement.remove();
|
|
434
|
+
this.styleElement = null;
|
|
435
|
+
}
|
|
436
|
+
return this;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* 更新状态
|
|
440
|
+
*/
|
|
441
|
+
update(state) {
|
|
442
|
+
if (state.markdown.length < this.lastMarkdownLength) {
|
|
443
|
+
this.appendHistory = [];
|
|
444
|
+
this.lastMarkdownLength = 0;
|
|
445
|
+
}
|
|
446
|
+
if (state.markdown.length > this.lastMarkdownLength) {
|
|
447
|
+
const chunk = state.markdown.slice(this.lastMarkdownLength);
|
|
448
|
+
this.appendHistory.push({
|
|
449
|
+
timestamp: Date.now(),
|
|
450
|
+
chunk,
|
|
451
|
+
completedCount: state.completedBlocks.length,
|
|
452
|
+
pendingCount: state.pendingBlocks.length
|
|
453
|
+
});
|
|
454
|
+
this.lastMarkdownLength = state.markdown.length;
|
|
455
|
+
}
|
|
456
|
+
this.state = state;
|
|
457
|
+
this.render();
|
|
458
|
+
return this;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* 重置状态
|
|
462
|
+
*/
|
|
463
|
+
reset() {
|
|
464
|
+
this.state = null;
|
|
465
|
+
this.appendHistory = [];
|
|
466
|
+
this.lastMarkdownLength = 0;
|
|
467
|
+
this.selectedBlockId = null;
|
|
468
|
+
this.render();
|
|
469
|
+
return this;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* 打开面板
|
|
473
|
+
*/
|
|
474
|
+
open() {
|
|
475
|
+
this.isOpen = true;
|
|
476
|
+
this.render();
|
|
477
|
+
return this;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* 关闭面板
|
|
481
|
+
*/
|
|
482
|
+
close() {
|
|
483
|
+
this.isOpen = false;
|
|
484
|
+
this.render();
|
|
485
|
+
return this;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* 切换面板
|
|
489
|
+
*/
|
|
490
|
+
toggle() {
|
|
491
|
+
this.isOpen = !this.isOpen;
|
|
492
|
+
this.render();
|
|
493
|
+
return this;
|
|
494
|
+
}
|
|
495
|
+
render() {
|
|
496
|
+
if (!this.container) return;
|
|
497
|
+
const state = this.state;
|
|
498
|
+
this.container.innerHTML = `
|
|
499
|
+
<button class="devtools-trigger" title="Incremark DevTools">
|
|
500
|
+
<span>\u{1F527}</span>
|
|
501
|
+
${state?.isLoading ? '<span class="loading-dot"></span>' : ""}
|
|
502
|
+
</button>
|
|
503
|
+
<div class="devtools-panel ${this.isOpen ? "open" : ""}">
|
|
504
|
+
<header class="devtools-header">
|
|
505
|
+
<h3>Incremark DevTools</h3>
|
|
506
|
+
<div class="devtools-tabs">
|
|
507
|
+
${["overview", "blocks", "ast", "timeline"].map((tab) => `
|
|
508
|
+
<button class="${this.activeTab === tab ? "active" : ""}" data-tab="${tab}">
|
|
509
|
+
${tab}
|
|
510
|
+
</button>
|
|
511
|
+
`).join("")}
|
|
512
|
+
</div>
|
|
513
|
+
<button class="close-btn">\xD7</button>
|
|
514
|
+
</header>
|
|
515
|
+
<main class="devtools-content">
|
|
516
|
+
${this.renderTabContent()}
|
|
517
|
+
</main>
|
|
518
|
+
</div>
|
|
519
|
+
`;
|
|
520
|
+
this.bindEvents();
|
|
521
|
+
}
|
|
522
|
+
renderTabContent() {
|
|
523
|
+
const state = this.state;
|
|
524
|
+
if (!state) {
|
|
525
|
+
return '<div style="color: #888; text-align: center;">\u7B49\u5F85\u6570\u636E...</div>';
|
|
526
|
+
}
|
|
527
|
+
switch (this.activeTab) {
|
|
528
|
+
case "overview":
|
|
529
|
+
return this.renderOverview(state);
|
|
530
|
+
case "blocks":
|
|
531
|
+
return this.renderBlocks(state);
|
|
532
|
+
case "ast":
|
|
533
|
+
return this.renderAst(state);
|
|
534
|
+
case "timeline":
|
|
535
|
+
return this.renderTimeline();
|
|
536
|
+
default:
|
|
537
|
+
return "";
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
renderOverview(state) {
|
|
541
|
+
const nodeTypeStats = {};
|
|
542
|
+
for (const block of state.blocks) {
|
|
543
|
+
const type = block.node.type;
|
|
544
|
+
nodeTypeStats[type] = (nodeTypeStats[type] || 0) + 1;
|
|
545
|
+
}
|
|
546
|
+
return `
|
|
547
|
+
<div class="stats-grid">
|
|
548
|
+
<div class="stat-card">
|
|
549
|
+
<div class="stat-value">${state.markdown.length}</div>
|
|
550
|
+
<div class="stat-label">\u5B57\u7B26</div>
|
|
551
|
+
</div>
|
|
552
|
+
<div class="stat-card">
|
|
553
|
+
<div class="stat-value">${state.blocks.length}</div>
|
|
554
|
+
<div class="stat-label">\u603B\u5757\u6570</div>
|
|
555
|
+
</div>
|
|
556
|
+
<div class="stat-card completed">
|
|
557
|
+
<div class="stat-value">${state.completedBlocks.length}</div>
|
|
558
|
+
<div class="stat-label">\u5DF2\u5B8C\u6210</div>
|
|
559
|
+
</div>
|
|
560
|
+
<div class="stat-card pending">
|
|
561
|
+
<div class="stat-value">${state.pendingBlocks.length}</div>
|
|
562
|
+
<div class="stat-label">\u5F85\u5904\u7406</div>
|
|
563
|
+
</div>
|
|
564
|
+
</div>
|
|
565
|
+
|
|
566
|
+
<div class="section">
|
|
567
|
+
<h4>\u8282\u70B9\u7C7B\u578B\u5206\u5E03</h4>
|
|
568
|
+
<div class="type-bars">
|
|
569
|
+
${Object.entries(nodeTypeStats).map(([type, count]) => `
|
|
570
|
+
<div class="type-bar">
|
|
571
|
+
<span class="type-color" style="background: ${NODE_TYPE_COLORS[type] || "#9ca3af"}"></span>
|
|
572
|
+
<span class="type-name">${type}</span>
|
|
573
|
+
<span class="type-count">${count}</span>
|
|
574
|
+
</div>
|
|
575
|
+
`).join("")}
|
|
576
|
+
</div>
|
|
577
|
+
</div>
|
|
578
|
+
|
|
579
|
+
<div class="section">
|
|
580
|
+
<h4>\u72B6\u6001</h4>
|
|
581
|
+
<div class="status-indicator ${state.isLoading ? "loading" : ""}">
|
|
582
|
+
${state.isLoading ? "\u{1F504} \u89E3\u6790\u4E2D..." : "\u2705 \u7A7A\u95F2"}
|
|
583
|
+
</div>
|
|
584
|
+
</div>
|
|
585
|
+
`;
|
|
586
|
+
}
|
|
587
|
+
renderBlocks(state) {
|
|
588
|
+
const selectedBlock = state.blocks.find((b) => b.id === this.selectedBlockId);
|
|
589
|
+
return `
|
|
590
|
+
<div class="blocks-list">
|
|
591
|
+
${state.blocks.map((block) => `
|
|
592
|
+
<div class="block-item ${block.id === this.selectedBlockId ? "selected" : ""}"
|
|
593
|
+
data-block-id="${block.id}">
|
|
594
|
+
<span class="block-status ${block.status}"></span>
|
|
595
|
+
<span class="block-type" style="color: ${NODE_TYPE_COLORS[block.node.type] || "#9ca3af"}">
|
|
596
|
+
${block.node.type}
|
|
597
|
+
</span>
|
|
598
|
+
<span class="block-preview">${this.truncate(block.rawText, 50)}</span>
|
|
599
|
+
</div>
|
|
600
|
+
`).join("")}
|
|
601
|
+
</div>
|
|
602
|
+
${selectedBlock ? `
|
|
603
|
+
<div class="block-detail">
|
|
604
|
+
<h4>\u5757\u8BE6\u60C5</h4>
|
|
605
|
+
<div class="detail-row">
|
|
606
|
+
<span class="label">ID:</span>
|
|
607
|
+
<span class="value">${selectedBlock.id}</span>
|
|
608
|
+
</div>
|
|
609
|
+
<div class="detail-row">
|
|
610
|
+
<span class="label">\u7C7B\u578B:</span>
|
|
611
|
+
<span class="value">${selectedBlock.node.type}</span>
|
|
612
|
+
</div>
|
|
613
|
+
<div class="detail-row">
|
|
614
|
+
<span class="label">\u72B6\u6001:</span>
|
|
615
|
+
<span class="value ${selectedBlock.status}">${selectedBlock.status}</span>
|
|
616
|
+
</div>
|
|
617
|
+
<div class="detail-row">
|
|
618
|
+
<span class="label">\u8303\u56F4:</span>
|
|
619
|
+
<span class="value">${selectedBlock.startOffset} - ${selectedBlock.endOffset}</span>
|
|
620
|
+
</div>
|
|
621
|
+
<h5>\u539F\u59CB\u6587\u672C</h5>
|
|
622
|
+
<pre class="raw-text">${this.escapeHtml(selectedBlock.rawText)}</pre>
|
|
623
|
+
<h5>AST \u8282\u70B9</h5>
|
|
624
|
+
<pre class="ast-json">${this.escapeHtml(JSON.stringify(selectedBlock.node, null, 2))}</pre>
|
|
625
|
+
</div>
|
|
626
|
+
` : ""}
|
|
627
|
+
`;
|
|
628
|
+
}
|
|
629
|
+
renderAst(state) {
|
|
630
|
+
return `<pre class="ast-tree">${this.escapeHtml(JSON.stringify(state.ast, null, 2))}</pre>`;
|
|
631
|
+
}
|
|
632
|
+
renderTimeline() {
|
|
633
|
+
return `
|
|
634
|
+
<div class="timeline-header">
|
|
635
|
+
<span>\u5171 ${this.appendHistory.length} \u6B21 append</span>
|
|
636
|
+
<button data-action="clear-history">\u6E05\u7A7A</button>
|
|
637
|
+
</div>
|
|
638
|
+
<div class="timeline-list">
|
|
639
|
+
${this.appendHistory.map((record, i) => `
|
|
640
|
+
<div class="timeline-item">
|
|
641
|
+
<span class="time">${this.formatTime(record.timestamp)}</span>
|
|
642
|
+
<span class="chunk">${this.escapeHtml(this.truncate(record.chunk.replace(/\n/g, "\u21B5"), 30))}</span>
|
|
643
|
+
<span class="stats">\u2705${record.completedCount} \u23F3${record.pendingCount}</span>
|
|
644
|
+
</div>
|
|
645
|
+
`).join("")}
|
|
646
|
+
</div>
|
|
647
|
+
`;
|
|
648
|
+
}
|
|
649
|
+
bindEvents() {
|
|
650
|
+
if (!this.container) return;
|
|
651
|
+
const trigger = this.container.querySelector(".devtools-trigger");
|
|
652
|
+
trigger?.addEventListener("click", () => this.toggle());
|
|
653
|
+
const closeBtn = this.container.querySelector(".close-btn");
|
|
654
|
+
closeBtn?.addEventListener("click", () => this.close());
|
|
655
|
+
const tabs = this.container.querySelectorAll(".devtools-tabs button");
|
|
656
|
+
tabs.forEach((tab) => {
|
|
657
|
+
tab.addEventListener("click", () => {
|
|
658
|
+
const tabName = tab.getAttribute("data-tab");
|
|
659
|
+
if (tabName) {
|
|
660
|
+
this.activeTab = tabName;
|
|
661
|
+
this.render();
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
const blockItems = this.container.querySelectorAll(".block-item");
|
|
666
|
+
blockItems.forEach((item) => {
|
|
667
|
+
item.addEventListener("click", () => {
|
|
668
|
+
const blockId = item.getAttribute("data-block-id");
|
|
669
|
+
if (blockId) {
|
|
670
|
+
this.selectedBlockId = blockId;
|
|
671
|
+
this.render();
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
const clearBtn = this.container.querySelector('[data-action="clear-history"]');
|
|
676
|
+
clearBtn?.addEventListener("click", () => {
|
|
677
|
+
this.appendHistory = [];
|
|
678
|
+
this.render();
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
truncate(str, len) {
|
|
682
|
+
if (str.length <= len) return str;
|
|
683
|
+
return str.slice(0, len) + "...";
|
|
684
|
+
}
|
|
685
|
+
formatTime(timestamp) {
|
|
686
|
+
return new Date(timestamp).toLocaleTimeString();
|
|
687
|
+
}
|
|
688
|
+
escapeHtml(str) {
|
|
689
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
function createDevTools(options) {
|
|
693
|
+
return new IncremarkDevTools(options);
|
|
694
|
+
}
|
|
695
|
+
function mountDevTools(options) {
|
|
696
|
+
const devtools = new IncremarkDevTools(options);
|
|
697
|
+
if (typeof document !== "undefined") {
|
|
698
|
+
if (document.readyState === "loading") {
|
|
699
|
+
document.addEventListener("DOMContentLoaded", () => devtools.mount());
|
|
700
|
+
} else {
|
|
701
|
+
devtools.mount();
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return (state) => {
|
|
705
|
+
const blocks = [
|
|
706
|
+
...state.completedBlocks.map((b) => ({ ...b, stableId: b.id })),
|
|
707
|
+
...state.pendingBlocks.map((b, i) => ({ ...b, stableId: `pending-${i}` }))
|
|
708
|
+
];
|
|
709
|
+
devtools.update({
|
|
710
|
+
blocks,
|
|
711
|
+
completedBlocks: state.completedBlocks,
|
|
712
|
+
pendingBlocks: state.pendingBlocks,
|
|
713
|
+
markdown: state.markdown,
|
|
714
|
+
ast: state.ast,
|
|
715
|
+
isLoading: state.pendingBlocks.length > 0
|
|
716
|
+
});
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
export {
|
|
720
|
+
IncremarkDevTools,
|
|
721
|
+
createDevTools,
|
|
722
|
+
mountDevTools
|
|
723
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@incremark/devtools",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Incremark DevTools - Framework-agnostic debugging panel",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./style.css": "./dist/style.css"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@incremark/core": "0.0.1"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"tsup": "^8.0.0",
|
|
25
|
+
"typescript": "^5.3.0"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup",
|
|
29
|
+
"dev": "tsup --watch"
|
|
30
|
+
}
|
|
31
|
+
}
|