@cluesmith/codev 1.1.0 → 1.2.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/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +19 -0
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
- package/dist/agent-farm/commands/cleanup.js +18 -1
- package/dist/agent-farm/commands/cleanup.js.map +1 -1
- package/dist/agent-farm/commands/consult.d.ts +16 -0
- package/dist/agent-farm/commands/consult.d.ts.map +1 -0
- package/dist/agent-farm/commands/consult.js +51 -0
- package/dist/agent-farm/commands/consult.js.map +1 -0
- package/dist/agent-farm/commands/open.js +6 -6
- package/dist/agent-farm/commands/open.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +51 -42
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/start.d.ts.map +1 -1
- package/dist/agent-farm/commands/start.js +9 -14
- package/dist/agent-farm/commands/start.js.map +1 -1
- package/dist/agent-farm/commands/util.js +2 -2
- package/dist/agent-farm/commands/util.js.map +1 -1
- package/dist/agent-farm/db/errors.d.ts +4 -0
- package/dist/agent-farm/db/errors.d.ts.map +1 -1
- package/dist/agent-farm/db/errors.js +8 -0
- package/dist/agent-farm/db/errors.js.map +1 -1
- package/dist/agent-farm/servers/dashboard-server.js +125 -71
- package/dist/agent-farm/servers/dashboard-server.js.map +1 -1
- package/dist/agent-farm/servers/open-server.d.ts +9 -0
- package/dist/agent-farm/servers/open-server.d.ts.map +1 -0
- package/dist/agent-farm/servers/{annotate-server.js → open-server.js} +17 -15
- package/dist/agent-farm/servers/open-server.js.map +1 -0
- package/dist/agent-farm/servers/tower-server.js +4 -7
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/state.d.ts +5 -0
- package/dist/agent-farm/state.d.ts.map +1 -1
- package/dist/agent-farm/state.js +17 -0
- package/dist/agent-farm/state.js.map +1 -1
- package/dist/agent-farm/types.d.ts +1 -1
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/agent-farm/utils/config.d.ts.map +1 -1
- package/dist/agent-farm/utils/config.js +13 -7
- package/dist/agent-farm/utils/config.js.map +1 -1
- package/dist/agent-farm/utils/port-registry.d.ts +1 -1
- package/dist/agent-farm/utils/port-registry.d.ts.map +1 -1
- package/dist/agent-farm/utils/port-registry.js +1 -1
- package/dist/agent-farm/utils/port-registry.js.map +1 -1
- package/dist/agent-farm/utils/shell.d.ts +19 -0
- package/dist/agent-farm/utils/shell.d.ts.map +1 -1
- package/dist/agent-farm/utils/shell.js +28 -0
- package/dist/agent-farm/utils/shell.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +33 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.d.ts +3 -0
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +31 -25
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/consult/index.d.ts +3 -2
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +128 -54
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +88 -36
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/eject.d.ts +18 -0
- package/dist/commands/eject.d.ts.map +1 -0
- package/dist/commands/eject.js +149 -0
- package/dist/commands/eject.js.map +1 -0
- package/dist/commands/import.d.ts +16 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +278 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +32 -27
- package/dist/commands/init.js.map +1 -1
- package/dist/lib/projectlist-parser.d.ts +70 -0
- package/dist/lib/projectlist-parser.d.ts.map +1 -0
- package/dist/lib/projectlist-parser.js +200 -0
- package/dist/lib/projectlist-parser.js.map +1 -0
- package/dist/lib/skeleton.d.ts +41 -0
- package/dist/lib/skeleton.d.ts.map +1 -0
- package/dist/lib/skeleton.js +110 -0
- package/dist/lib/skeleton.js.map +1 -0
- package/dist/lib/templates.d.ts +2 -1
- package/dist/lib/templates.d.ts.map +1 -1
- package/dist/lib/templates.js +11 -10
- package/dist/lib/templates.js.map +1 -1
- package/package.json +5 -4
- package/{templates → skeleton}/DEPENDENCIES.md +3 -48
- package/skeleton/bin/agent-farm +7 -0
- package/skeleton/docs/commands/agent-farm.md +469 -0
- package/skeleton/docs/commands/codev.md +253 -0
- package/skeleton/docs/commands/consult.md +286 -0
- package/skeleton/docs/commands/overview.md +108 -0
- package/skeleton/maintain/.gitkeep +2 -0
- package/{templates → skeleton}/protocols/experiment/protocol.md +2 -2
- package/skeleton/protocols/maintain/protocol.md +502 -0
- package/skeleton/protocols/maintain/templates/maintenance-run.md +64 -0
- package/{templates → skeleton}/protocols/spider/protocol.md +9 -9
- package/{templates/protocols/spider-solo → skeleton/protocols/spider}/templates/plan.md +22 -1
- package/{templates/protocols/spider-solo → skeleton/protocols/spider}/templates/spec.md +30 -1
- package/skeleton/protocols/tick/protocol.md +277 -0
- package/skeleton/resources/lessons-learned.md +30 -0
- package/skeleton/resources/workflow-reference.md +242 -0
- package/skeleton/roles/architect.md +283 -0
- package/{templates → skeleton}/roles/builder.md +2 -0
- package/skeleton/roles/review-types/impl-review.md +56 -0
- package/skeleton/roles/review-types/integration-review.md +68 -0
- package/skeleton/roles/review-types/plan-review.md +59 -0
- package/skeleton/roles/review-types/pr-ready.md +72 -0
- package/skeleton/roles/review-types/spec-review.md +55 -0
- package/skeleton/templates/lessons-learned.md +28 -0
- package/{templates → skeleton}/templates/projectlist.md +17 -16
- package/dist/agent-farm/servers/annotate-server.d.ts +0 -9
- package/dist/agent-farm/servers/annotate-server.d.ts.map +0 -1
- package/dist/agent-farm/servers/annotate-server.js.map +0 -1
- package/templates/agents/architecture-documenter.md +0 -189
- package/templates/agents/codev-updater.md +0 -276
- package/templates/agents/spider-protocol-updater.md +0 -118
- package/templates/annotate.html +0 -903
- package/templates/bin/agent-farm +0 -18
- package/templates/bin/annotate-server.js +0 -140
- package/templates/dashboard-split.html +0 -1679
- package/templates/dashboard.html +0 -149
- package/templates/protocols/maintain/protocol.md +0 -235
- package/templates/protocols/spider/templates/plan.md +0 -169
- package/templates/protocols/spider/templates/review.md +0 -207
- package/templates/protocols/spider/templates/spec.md +0 -140
- package/templates/protocols/spider-solo/protocol.md +0 -619
- package/templates/protocols/tick/protocol.md +0 -250
- package/templates/roles/architect.md +0 -230
- package/templates/tower.html +0 -1032
- /package/{templates/AGENTS.md → skeleton/AGENTS.md.template} +0 -0
- /package/{templates/CLAUDE.md → skeleton/CLAUDE.md.template} +0 -0
- /package/{templates → skeleton}/bin/codev-doctor +0 -0
- /package/{templates → skeleton}/builders.md +0 -0
- /package/{templates → skeleton}/config.json +0 -0
- /package/{templates → skeleton}/plans/.gitkeep +0 -0
- /package/{templates → skeleton}/protocols/experiment/templates/notes.md +0 -0
- /package/{templates/protocols/spider-solo → skeleton/protocols/spider}/templates/review.md +0 -0
- /package/{templates → skeleton}/protocols/tick/templates/plan.md +0 -0
- /package/{templates → skeleton}/protocols/tick/templates/review.md +0 -0
- /package/{templates → skeleton}/protocols/tick/templates/spec.md +0 -0
- /package/{templates → skeleton}/reviews/.gitkeep +0 -0
- /package/{templates → skeleton}/roles/consultant.md +0 -0
- /package/{templates → skeleton}/specs/.gitkeep +0 -0
package/templates/annotate.html
DELETED
|
@@ -1,903 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<title>Annotate: {{FILE}}</title>
|
|
6
|
-
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet">
|
|
7
|
-
<style>
|
|
8
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
-
body {
|
|
10
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
11
|
-
background: #1a1a1a;
|
|
12
|
-
color: #fff;
|
|
13
|
-
min-height: 100vh;
|
|
14
|
-
}
|
|
15
|
-
.header {
|
|
16
|
-
padding: 15px 20px;
|
|
17
|
-
background: #2a2a2a;
|
|
18
|
-
border-bottom: 1px solid #333;
|
|
19
|
-
position: sticky;
|
|
20
|
-
top: 0;
|
|
21
|
-
z-index: 100;
|
|
22
|
-
}
|
|
23
|
-
.header h1.path { font-size: 14px; font-weight: 500; font-family: monospace; color: #fff; }
|
|
24
|
-
.header .subtitle { color: #888; font-size: 12px; margin-top: 4px; }
|
|
25
|
-
.header .actions { margin-top: 10px; display: flex; align-items: center; gap: 8px; }
|
|
26
|
-
.header .btn {
|
|
27
|
-
padding: 6px 12px; border-radius: 4px; border: none; cursor: pointer;
|
|
28
|
-
font-size: 12px;
|
|
29
|
-
}
|
|
30
|
-
.btn-primary { background: #3b82f6; color: #fff; }
|
|
31
|
-
.btn-secondary { background: #444; color: #fff; }
|
|
32
|
-
.btn-success { background: #22c55e; color: #fff; }
|
|
33
|
-
.btn:hover { opacity: 0.9; }
|
|
34
|
-
|
|
35
|
-
/* Unsaved indicator */
|
|
36
|
-
#unsavedIndicator {
|
|
37
|
-
color: #fbbf24;
|
|
38
|
-
font-size: 12px;
|
|
39
|
-
display: none;
|
|
40
|
-
}
|
|
41
|
-
#unsavedIndicator::before {
|
|
42
|
-
content: '●';
|
|
43
|
-
margin-right: 4px;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/* Grid layout: each row has line number + code, guaranteeing alignment */
|
|
47
|
-
.content {
|
|
48
|
-
display: grid;
|
|
49
|
-
grid-template-columns: auto 1fr;
|
|
50
|
-
padding: 15px 0;
|
|
51
|
-
}
|
|
52
|
-
.line-num {
|
|
53
|
-
background: #252525;
|
|
54
|
-
color: #666;
|
|
55
|
-
text-align: right;
|
|
56
|
-
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
|
57
|
-
font-size: 13px;
|
|
58
|
-
user-select: none;
|
|
59
|
-
border-right: 1px solid #333;
|
|
60
|
-
padding: 0 12px 0 8px;
|
|
61
|
-
cursor: pointer;
|
|
62
|
-
line-height: 1.5;
|
|
63
|
-
min-width: 50px;
|
|
64
|
-
position: sticky;
|
|
65
|
-
left: 0;
|
|
66
|
-
}
|
|
67
|
-
.line-num:hover { background: #333; color: #fff; }
|
|
68
|
-
.line-num.has-review {
|
|
69
|
-
background: rgba(250, 204, 21, 0.2);
|
|
70
|
-
color: #facc15;
|
|
71
|
-
}
|
|
72
|
-
.code-line {
|
|
73
|
-
background: #1a1a1a;
|
|
74
|
-
padding: 0 15px;
|
|
75
|
-
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
|
76
|
-
font-size: 13px;
|
|
77
|
-
line-height: 1.5;
|
|
78
|
-
white-space: pre-wrap;
|
|
79
|
-
word-wrap: break-word;
|
|
80
|
-
}
|
|
81
|
-
/* Hybrid markdown: syntax visible but muted, content styled */
|
|
82
|
-
/* All text uses same font size for consistent line heights in annotation view */
|
|
83
|
-
.md-syntax { color: #555; } /* Muted syntax markers */
|
|
84
|
-
.md-h1, .md-h2, .md-h3, .md-h4, .md-h5, .md-h6 { font-weight: bold; color: #c678dd; }
|
|
85
|
-
.md-bold { font-weight: bold; color: #e5c07b; }
|
|
86
|
-
.md-italic { font-style: italic; color: #98c379; }
|
|
87
|
-
.md-code { color: #e06c75; background: #2c313a; padding: 1px 4px; border-radius: 3px; }
|
|
88
|
-
.md-link-text { color: #61afef; text-decoration: underline; }
|
|
89
|
-
.md-link-url { color: #555; font-size: 0.9em; }
|
|
90
|
-
.md-bullet { color: #56b6c2; }
|
|
91
|
-
.md-blockquote { color: #abb2bf; border-left: 3px solid #555; padding-left: 10px; }
|
|
92
|
-
.md-fence { color: #555; }
|
|
93
|
-
.md-codeblock { color: #98c379; background: #2c313a; display: block; }
|
|
94
|
-
|
|
95
|
-
/* Editor textarea */
|
|
96
|
-
#editor {
|
|
97
|
-
display: none;
|
|
98
|
-
width: 100%;
|
|
99
|
-
height: calc(100vh - 80px);
|
|
100
|
-
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
|
101
|
-
font-size: 13px;
|
|
102
|
-
line-height: 1.5;
|
|
103
|
-
padding: 15px;
|
|
104
|
-
border: none;
|
|
105
|
-
resize: none;
|
|
106
|
-
background: #1a1a1a;
|
|
107
|
-
color: #d4d4d4;
|
|
108
|
-
outline: none;
|
|
109
|
-
tab-size: 2;
|
|
110
|
-
}
|
|
111
|
-
#editor:focus {
|
|
112
|
-
outline: none;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/* Notification toast */
|
|
116
|
-
.notification {
|
|
117
|
-
position: fixed;
|
|
118
|
-
bottom: 20px;
|
|
119
|
-
right: 20px;
|
|
120
|
-
padding: 12px 20px;
|
|
121
|
-
background: #22c55e;
|
|
122
|
-
color: white;
|
|
123
|
-
border-radius: 6px;
|
|
124
|
-
font-size: 14px;
|
|
125
|
-
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|
126
|
-
z-index: 300;
|
|
127
|
-
animation: slideIn 0.3s ease;
|
|
128
|
-
}
|
|
129
|
-
.notification.error {
|
|
130
|
-
background: #ef4444;
|
|
131
|
-
}
|
|
132
|
-
@keyframes slideIn {
|
|
133
|
-
from { transform: translateX(100%); opacity: 0; }
|
|
134
|
-
to { transform: translateX(0); opacity: 1; }
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.review-line {
|
|
138
|
-
background: rgba(250, 204, 21, 0.1);
|
|
139
|
-
display: block;
|
|
140
|
-
border-left: 3px solid #facc15;
|
|
141
|
-
padding-left: 5px;
|
|
142
|
-
margin-left: -8px;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/* Comment Dialog - positioned popup */
|
|
146
|
-
.overlay {
|
|
147
|
-
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
|
148
|
-
background: rgba(0,0,0,0.3);
|
|
149
|
-
display: none;
|
|
150
|
-
z-index: 200;
|
|
151
|
-
}
|
|
152
|
-
.overlay.active { display: block; }
|
|
153
|
-
|
|
154
|
-
.dialog {
|
|
155
|
-
position: absolute;
|
|
156
|
-
background: #2a2a2a;
|
|
157
|
-
padding: 20px;
|
|
158
|
-
border-radius: 8px;
|
|
159
|
-
width: 700px;
|
|
160
|
-
max-width: 90vw;
|
|
161
|
-
box-shadow: 0 10px 40px rgba(0,0,0,0.5);
|
|
162
|
-
border: 1px solid #444;
|
|
163
|
-
}
|
|
164
|
-
.dialog h3 { font-size: 16px; margin-bottom: 5px; }
|
|
165
|
-
.dialog .line-info { color: #888; font-size: 13px; margin-bottom: 15px; }
|
|
166
|
-
.dialog .line-preview {
|
|
167
|
-
background: #1a1a1a; padding: 10px; border-radius: 4px;
|
|
168
|
-
font-family: monospace; font-size: 12px; margin-bottom: 15px;
|
|
169
|
-
overflow-x: auto; white-space: pre;
|
|
170
|
-
}
|
|
171
|
-
.dialog textarea {
|
|
172
|
-
width: 100%; height: 100px;
|
|
173
|
-
background: #1a1a1a; border: 1px solid #444;
|
|
174
|
-
color: #fff; padding: 10px; border-radius: 4px;
|
|
175
|
-
font-family: inherit; font-size: 14px;
|
|
176
|
-
resize: vertical;
|
|
177
|
-
}
|
|
178
|
-
.dialog textarea:focus { outline: none; border-color: #3b82f6; }
|
|
179
|
-
.dialog .actions { margin-top: 15px; text-align: right; }
|
|
180
|
-
.dialog .btn { margin-left: 8px; }
|
|
181
|
-
.btn-danger { background: #ef4444; color: #fff; }
|
|
182
|
-
|
|
183
|
-
/* Annotations list */
|
|
184
|
-
.annotations-panel {
|
|
185
|
-
position: fixed; right: 0; top: 0; bottom: 0; width: 300px;
|
|
186
|
-
background: #252525; border-left: 1px solid #333;
|
|
187
|
-
transform: translateX(100%);
|
|
188
|
-
transition: transform 0.2s;
|
|
189
|
-
z-index: 150;
|
|
190
|
-
overflow-y: auto;
|
|
191
|
-
}
|
|
192
|
-
.annotations-panel.open { transform: translateX(0); }
|
|
193
|
-
.annotations-panel h3 { padding: 15px; border-bottom: 1px solid #333; font-size: 14px; }
|
|
194
|
-
.annotation-item {
|
|
195
|
-
padding: 12px 15px; border-bottom: 1px solid #333; cursor: pointer;
|
|
196
|
-
}
|
|
197
|
-
.annotation-item:hover { background: #2a2a2a; }
|
|
198
|
-
.annotation-item .line { color: #3b82f6; font-size: 12px; }
|
|
199
|
-
.annotation-item .text { font-size: 13px; margin-top: 4px; }
|
|
200
|
-
</style>
|
|
201
|
-
</head>
|
|
202
|
-
<body data-builder="{{BUILDER_ID}}" data-file="{{FILE_PATH}}" data-lang="{{LANG}}">
|
|
203
|
-
<div class="header">
|
|
204
|
-
<h1 class="path">{{FILE_PATH}}</h1>
|
|
205
|
-
<div class="subtitle">Click on a line number to leave an annotation.</div>
|
|
206
|
-
<div class="actions">
|
|
207
|
-
<button id="reloadBtn" class="btn btn-secondary" onclick="reloadFile()" title="Reload from disk" aria-label="Reload file from disk">↻</button>
|
|
208
|
-
<button id="editBtn" class="btn btn-primary" onclick="toggleEditMode()">Switch to Editing</button>
|
|
209
|
-
<button id="saveBtn" class="btn btn-success" onclick="saveEdit()" style="display: none;">Save</button>
|
|
210
|
-
<button id="cancelBtn" class="btn btn-secondary" onclick="cancelEdit()" style="display: none;">Cancel</button>
|
|
211
|
-
<span id="unsavedIndicator">Unsaved changes</span>
|
|
212
|
-
</div>
|
|
213
|
-
</div>
|
|
214
|
-
|
|
215
|
-
<div class="content" id="viewMode">
|
|
216
|
-
<!-- Grid: alternating line-num and code-line elements -->
|
|
217
|
-
</div>
|
|
218
|
-
|
|
219
|
-
<!-- Editor mode -->
|
|
220
|
-
<textarea id="editor" spellcheck="false"></textarea>
|
|
221
|
-
|
|
222
|
-
<!-- Comment Dialog -->
|
|
223
|
-
<div class="overlay" id="overlay">
|
|
224
|
-
<div class="dialog">
|
|
225
|
-
<h3 id="dialogTitle">Add Review Comment</h3>
|
|
226
|
-
<div class="line-info">Line <span id="dialogLine"></span></div>
|
|
227
|
-
<div class="line-preview" id="dialogPreview"></div>
|
|
228
|
-
<textarea id="commentText" placeholder="Enter your review comment..."></textarea>
|
|
229
|
-
<div class="hint" style="color: #666; font-size: 12px; margin-top: 8px;">Tip: Press Enter 3 times to submit</div>
|
|
230
|
-
<div class="actions">
|
|
231
|
-
<button class="btn btn-secondary" onclick="closeDialog()">Cancel</button>
|
|
232
|
-
<button class="btn btn-danger" onclick="resolveComment()" id="resolveBtn" style="display:none">Resolve</button>
|
|
233
|
-
<button class="btn btn-primary" onclick="saveComment()">Add Comment</button>
|
|
234
|
-
</div>
|
|
235
|
-
</div>
|
|
236
|
-
</div>
|
|
237
|
-
|
|
238
|
-
<!-- Annotations Panel -->
|
|
239
|
-
<div class="annotations-panel" id="annotationsPanel">
|
|
240
|
-
<h3>Review Comments</h3>
|
|
241
|
-
<div id="annotationsList"></div>
|
|
242
|
-
</div>
|
|
243
|
-
|
|
244
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
|
|
245
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js"></script>
|
|
246
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-typescript.min.js"></script>
|
|
247
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script>
|
|
248
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script>
|
|
249
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-markdown.min.js"></script>
|
|
250
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
|
|
251
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-yaml.min.js"></script>
|
|
252
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-css.min.js"></script>
|
|
253
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-markup.min.js"></script>
|
|
254
|
-
|
|
255
|
-
<script>
|
|
256
|
-
// State
|
|
257
|
-
let fileLines = [];
|
|
258
|
-
let currentLine = null;
|
|
259
|
-
let isEditing = false; // For comment dialog editing
|
|
260
|
-
let consecutiveEnters = 0; // Track triple-enter to submit
|
|
261
|
-
|
|
262
|
-
// Edit mode state
|
|
263
|
-
let editMode = false;
|
|
264
|
-
let originalContent = '';
|
|
265
|
-
let currentContent = '';
|
|
266
|
-
let hasUnsavedChanges = false;
|
|
267
|
-
|
|
268
|
-
// Comment patterns by file extension
|
|
269
|
-
const COMMENT_PATTERNS = {
|
|
270
|
-
js: { prefix: '// REVIEW', regex: /^(\s*)\/\/\s*REVIEW(\(@\w+\))?:\s*(.*)$/ },
|
|
271
|
-
ts: { prefix: '// REVIEW', regex: /^(\s*)\/\/\s*REVIEW(\(@\w+\))?:\s*(.*)$/ },
|
|
272
|
-
jsx: { prefix: '// REVIEW', regex: /^(\s*)\/\/\s*REVIEW(\(@\w+\))?:\s*(.*)$/ },
|
|
273
|
-
tsx: { prefix: '// REVIEW', regex: /^(\s*)\/\/\s*REVIEW(\(@\w+\))?:\s*(.*)$/ },
|
|
274
|
-
py: { prefix: '# REVIEW', regex: /^(\s*)#\s*REVIEW(\(@\w+\))?:\s*(.*)$/ },
|
|
275
|
-
sh: { prefix: '# REVIEW', regex: /^(\s*)#\s*REVIEW(\(@\w+\))?:\s*(.*)$/ },
|
|
276
|
-
bash: { prefix: '# REVIEW', regex: /^(\s*)#\s*REVIEW(\(@\w+\))?:\s*(.*)$/ },
|
|
277
|
-
yaml: { prefix: '# REVIEW', regex: /^(\s*)#\s*REVIEW(\(@\w+\))?:\s*(.*)$/ },
|
|
278
|
-
yml: { prefix: '# REVIEW', regex: /^(\s*)#\s*REVIEW(\(@\w+\))?:\s*(.*)$/ },
|
|
279
|
-
md: { prefix: '<!-- REVIEW', suffix: ' -->', regex: /^(\s*)<!--\s*REVIEW(\(@\w+\))?:\s*(.*?)\s*-->$/ },
|
|
280
|
-
html: { prefix: '<!-- REVIEW', suffix: ' -->', regex: /^(\s*)<!--\s*REVIEW(\(@\w+\))?:\s*(.*?)\s*-->$/ },
|
|
281
|
-
css: { prefix: '/* REVIEW', suffix: ' */', regex: /^(\s*)\/\*\s*REVIEW(\(@\w+\))?:\s*(.*?)\s*\*\/$/ },
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
// Get file extension and language
|
|
285
|
-
const filePath = document.body.dataset.file;
|
|
286
|
-
const ext = filePath.split('.').pop().toLowerCase();
|
|
287
|
-
const lang = document.body.dataset.lang || ext;
|
|
288
|
-
const commentStyle = COMMENT_PATTERNS[ext] || COMMENT_PATTERNS.js;
|
|
289
|
-
|
|
290
|
-
// Initialize
|
|
291
|
-
function init(content) {
|
|
292
|
-
currentContent = content;
|
|
293
|
-
originalContent = content;
|
|
294
|
-
fileLines = content.split('\n');
|
|
295
|
-
renderFile();
|
|
296
|
-
updateAnnotationsList();
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function renderFile() {
|
|
300
|
-
const viewMode = document.getElementById('viewMode');
|
|
301
|
-
|
|
302
|
-
// Render code with syntax highlighting - highlight LINE BY LINE
|
|
303
|
-
const isMarkdown = lang === 'markdown' || lang === 'md';
|
|
304
|
-
const prismLang = Prism.languages[lang] || Prism.languages.plaintext;
|
|
305
|
-
|
|
306
|
-
// Reset markdown state before rendering
|
|
307
|
-
if (isMarkdown) resetMarkdownState();
|
|
308
|
-
|
|
309
|
-
// For markdown: two-pass table rendering
|
|
310
|
-
let tableMap = new Map();
|
|
311
|
-
if (isMarkdown) {
|
|
312
|
-
const codeBlockRanges = findCodeBlockRanges(fileLines);
|
|
313
|
-
const tables = identifyTables(fileLines, codeBlockRanges);
|
|
314
|
-
tableMap = buildTableMap(tables);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Generate grid rows: each row has [line-num, code-line]
|
|
318
|
-
const gridHtml = fileLines.map((line, i) => {
|
|
319
|
-
let lineToRender = line;
|
|
320
|
-
|
|
321
|
-
// For markdown tables: pad cells for alignment
|
|
322
|
-
if (isMarkdown) {
|
|
323
|
-
const tableInfo = tableMap.get(i);
|
|
324
|
-
if (tableInfo) {
|
|
325
|
-
const isSep = isSeparatorRow(line);
|
|
326
|
-
lineToRender = renderTableRow(line, tableInfo.columns, isSep);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Use custom hybrid renderer for markdown
|
|
331
|
-
const highlighted = isMarkdown ? renderMarkdownLine(lineToRender) : Prism.highlight(lineToRender, prismLang, lang);
|
|
332
|
-
const isReview = commentStyle.regex.test(line);
|
|
333
|
-
const codeContent = isReview ? `<span class="review-line">${highlighted}</span>` : highlighted;
|
|
334
|
-
|
|
335
|
-
const hasReviewClass = isReview ? 'has-review' : '';
|
|
336
|
-
return `<span class="line-num ${hasReviewClass}" onclick="openDialog(${i}, event)">${i + 1}</span><div class="code-line" data-line="${i}">${codeContent || ' '}</div>`;
|
|
337
|
-
}).join('');
|
|
338
|
-
|
|
339
|
-
viewMode.innerHTML = gridHtml;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function openDialog(lineIndex, event) {
|
|
343
|
-
currentLine = lineIndex;
|
|
344
|
-
const line = fileLines[lineIndex];
|
|
345
|
-
const match = line.match(commentStyle.regex);
|
|
346
|
-
|
|
347
|
-
document.getElementById('dialogLine').textContent = lineIndex + 1;
|
|
348
|
-
|
|
349
|
-
// Show prior 3 lines + current line
|
|
350
|
-
const startLine = Math.max(0, lineIndex - 3);
|
|
351
|
-
const previewLines = [];
|
|
352
|
-
for (let i = startLine; i <= lineIndex; i++) {
|
|
353
|
-
const lineNum = (i + 1).toString().padStart(4, ' ');
|
|
354
|
-
const marker = i === lineIndex ? '→' : ' ';
|
|
355
|
-
previewLines.push(`${lineNum}${marker} ${fileLines[i]}`);
|
|
356
|
-
}
|
|
357
|
-
document.getElementById('dialogPreview').textContent = previewLines.join('\n');
|
|
358
|
-
|
|
359
|
-
if (match) {
|
|
360
|
-
// Editing existing comment
|
|
361
|
-
isEditing = true;
|
|
362
|
-
document.getElementById('dialogTitle').textContent = 'Edit Review Comment';
|
|
363
|
-
document.getElementById('commentText').value = match[3] || '';
|
|
364
|
-
document.getElementById('resolveBtn').style.display = 'inline-block';
|
|
365
|
-
} else {
|
|
366
|
-
// Adding new comment
|
|
367
|
-
isEditing = false;
|
|
368
|
-
document.getElementById('dialogTitle').textContent = 'Add Review Comment';
|
|
369
|
-
document.getElementById('commentText').value = '';
|
|
370
|
-
document.getElementById('resolveBtn').style.display = 'none';
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Position dialog next to click
|
|
374
|
-
const dialog = document.querySelector('.dialog');
|
|
375
|
-
const rect = event.target.getBoundingClientRect();
|
|
376
|
-
const dialogWidth = 700;
|
|
377
|
-
const dialogHeight = 350;
|
|
378
|
-
|
|
379
|
-
// Position to the right of the line number, with some padding
|
|
380
|
-
let left = rect.right + 10;
|
|
381
|
-
let top = rect.top;
|
|
382
|
-
|
|
383
|
-
// Keep dialog in viewport
|
|
384
|
-
if (left + dialogWidth > window.innerWidth) {
|
|
385
|
-
left = window.innerWidth - dialogWidth - 20;
|
|
386
|
-
}
|
|
387
|
-
if (top + dialogHeight > window.innerHeight) {
|
|
388
|
-
top = window.innerHeight - dialogHeight - 20;
|
|
389
|
-
}
|
|
390
|
-
if (top < 60) top = 60; // Below header
|
|
391
|
-
|
|
392
|
-
dialog.style.left = left + 'px';
|
|
393
|
-
dialog.style.top = top + 'px';
|
|
394
|
-
|
|
395
|
-
document.getElementById('overlay').classList.add('active');
|
|
396
|
-
document.getElementById('commentText').focus();
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function closeDialog() {
|
|
400
|
-
document.getElementById('overlay').classList.remove('active');
|
|
401
|
-
currentLine = null;
|
|
402
|
-
isEditing = false;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Open dialog from annotation panel (center it since no click position)
|
|
406
|
-
function openDialogFromPanel(lineIndex) {
|
|
407
|
-
// Create a fake event positioned at the line number
|
|
408
|
-
// Exclude continuation spans (..) so indexing matches fileLines
|
|
409
|
-
const lineNums = document.querySelectorAll('.line-num:not(.continuation)');
|
|
410
|
-
if (lineNums[lineIndex]) {
|
|
411
|
-
const fakeEvent = { target: lineNums[lineIndex] };
|
|
412
|
-
openDialog(lineIndex, fakeEvent);
|
|
413
|
-
// Scroll to the line
|
|
414
|
-
lineNums[lineIndex].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function saveComment() {
|
|
419
|
-
const text = document.getElementById('commentText').value.trim();
|
|
420
|
-
if (!text) return;
|
|
421
|
-
|
|
422
|
-
const line = fileLines[currentLine];
|
|
423
|
-
const indent = line.match(/^(\s*)/)[1];
|
|
424
|
-
|
|
425
|
-
let commentLine = indent + commentStyle.prefix + '(@architect): ' + text;
|
|
426
|
-
if (commentStyle.suffix) {
|
|
427
|
-
commentLine += commentStyle.suffix;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
if (isEditing) {
|
|
431
|
-
// Replace existing comment
|
|
432
|
-
fileLines[currentLine] = commentLine;
|
|
433
|
-
} else {
|
|
434
|
-
// Insert new comment BELOW the current line (so the annotated line keeps its number)
|
|
435
|
-
fileLines.splice(currentLine + 1, 0, commentLine);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
renderFile();
|
|
439
|
-
updateAnnotationsList();
|
|
440
|
-
closeDialog();
|
|
441
|
-
saveFile(); // Auto-save
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
function resolveComment() {
|
|
445
|
-
if (currentLine !== null && isEditing) {
|
|
446
|
-
fileLines.splice(currentLine, 1);
|
|
447
|
-
renderFile();
|
|
448
|
-
updateAnnotationsList();
|
|
449
|
-
closeDialog();
|
|
450
|
-
saveFile(); // Auto-save
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function updateAnnotationsList() {
|
|
455
|
-
const list = document.getElementById('annotationsList');
|
|
456
|
-
const annotations = [];
|
|
457
|
-
|
|
458
|
-
fileLines.forEach((line, i) => {
|
|
459
|
-
const match = line.match(commentStyle.regex);
|
|
460
|
-
if (match) {
|
|
461
|
-
annotations.push({
|
|
462
|
-
line: i + 1,
|
|
463
|
-
author: match[2] || '',
|
|
464
|
-
text: match[3] || ''
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
if (annotations.length === 0) {
|
|
470
|
-
list.innerHTML = '<div style="padding: 15px; color: #666;">No review comments</div>';
|
|
471
|
-
} else {
|
|
472
|
-
list.innerHTML = annotations.map(a => `
|
|
473
|
-
<div class="annotation-item" onclick="openDialogFromPanel(${a.line - 1})">
|
|
474
|
-
<div class="line">Line ${a.line} ${a.author}</div>
|
|
475
|
-
<div class="text">${escapeHtml(a.text)}</div>
|
|
476
|
-
</div>
|
|
477
|
-
`).join('');
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
function toggleAnnotations() {
|
|
482
|
-
document.getElementById('annotationsPanel').classList.toggle('open');
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
function escapeHtml(text) {
|
|
486
|
-
const div = document.createElement('div');
|
|
487
|
-
div.textContent = text;
|
|
488
|
-
return div.innerHTML;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Hybrid markdown: syntax visible but muted, content styled
|
|
492
|
-
// State for multi-line constructs
|
|
493
|
-
let inCodeBlock = false;
|
|
494
|
-
|
|
495
|
-
function renderMarkdownLine(line) {
|
|
496
|
-
// Handle code block fences
|
|
497
|
-
const fenceMatch = line.match(/^(\s*)(```+)(\w*)$/);
|
|
498
|
-
if (fenceMatch) {
|
|
499
|
-
inCodeBlock = !inCodeBlock;
|
|
500
|
-
return `<span class="md-fence">${escapeHtml(line)}</span>`;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// Inside code block - render as code, no markdown processing
|
|
504
|
-
if (inCodeBlock) {
|
|
505
|
-
return `<span class="md-codeblock">${escapeHtml(line)}</span>`;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
let html = escapeHtml(line);
|
|
509
|
-
|
|
510
|
-
// Headers: # visible but muted, content styled
|
|
511
|
-
const headerMatch = html.match(/^(#{1,6})\s+(.*)$/);
|
|
512
|
-
if (headerMatch) {
|
|
513
|
-
const level = headerMatch[1].length;
|
|
514
|
-
const hashes = headerMatch[1];
|
|
515
|
-
const content = headerMatch[2];
|
|
516
|
-
return `<span class="md-syntax">${hashes}</span> <span class="md-h${level}">${content}</span>`;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// List items: - or * visible but styled
|
|
520
|
-
html = html.replace(/^(\s*)([-*])\s+/, '$1<span class="md-bullet">$2</span> ');
|
|
521
|
-
html = html.replace(/^(\s*)(\d+\.)\s+/, '$1<span class="md-bullet">$2</span> ');
|
|
522
|
-
|
|
523
|
-
// Bold: ** visible but muted, content bold
|
|
524
|
-
html = html.replace(/\*\*([^*]+)\*\*/g, '<span class="md-syntax">**</span><span class="md-bold">$1</span><span class="md-syntax">**</span>');
|
|
525
|
-
|
|
526
|
-
// Italic: * visible but muted, content italic (but not inside **)
|
|
527
|
-
html = html.replace(/(?<!\*)(?<!\<span class="md-syntax"\>)\*([^*]+)\*(?!\*)/g, '<span class="md-syntax">*</span><span class="md-italic">$1</span><span class="md-syntax">*</span>');
|
|
528
|
-
|
|
529
|
-
// Inline code: ` visible but muted, content styled
|
|
530
|
-
html = html.replace(/`([^`]+)`/g, '<span class="md-syntax">`</span><span class="md-code">$1</span><span class="md-syntax">`</span>');
|
|
531
|
-
|
|
532
|
-
// Links: [text](url) - brackets muted, text styled, url muted
|
|
533
|
-
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<span class="md-syntax">[</span><span class="md-link-text">$1</span><span class="md-syntax">](</span><span class="md-link-url">$2</span><span class="md-syntax">)</span>');
|
|
534
|
-
|
|
535
|
-
// Blockquote: > visible but muted
|
|
536
|
-
html = html.replace(/^(\s*)(>)\s*(.*)$/, '$1<span class="md-syntax">$2</span> <span class="md-blockquote">$3</span>');
|
|
537
|
-
|
|
538
|
-
return html;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// Reset code block state when re-rendering
|
|
542
|
-
function resetMarkdownState() {
|
|
543
|
-
inCodeBlock = false;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// ==================== Table Alignment Functions ====================
|
|
547
|
-
|
|
548
|
-
// Find code block ranges to exclude from table processing
|
|
549
|
-
function findCodeBlockRanges(lines) {
|
|
550
|
-
const ranges = [];
|
|
551
|
-
let start = null;
|
|
552
|
-
|
|
553
|
-
lines.forEach((line, i) => {
|
|
554
|
-
if (/^(\s*)(```+|~~~+)/.test(line)) {
|
|
555
|
-
if (start === null) {
|
|
556
|
-
start = i;
|
|
557
|
-
} else {
|
|
558
|
-
ranges.push({ start, end: i });
|
|
559
|
-
start = null;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
// Handle unclosed code block (extends to end of file)
|
|
565
|
-
if (start !== null) {
|
|
566
|
-
ranges.push({ start, end: lines.length - 1 });
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
return ranges;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// Check if line is inside a code block
|
|
573
|
-
function isInsideCodeBlock(lineNum, ranges) {
|
|
574
|
-
return ranges.some(r => lineNum >= r.start && lineNum <= r.end);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// Check if line is a table separator row (e.g., |---|---|)
|
|
578
|
-
function isSeparatorRow(line) {
|
|
579
|
-
// Must contain at least one dash and be mostly dashes, pipes, colons, and spaces
|
|
580
|
-
return /^\|?[\s:|-]+\|?$/.test(line) && line.includes('-');
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// Parse cells from a table row, handling leading/trailing pipes
|
|
584
|
-
function parseTableCells(line) {
|
|
585
|
-
// Split by | (not escaped \|)
|
|
586
|
-
const parts = line.split(/(?<!\\)\|/);
|
|
587
|
-
|
|
588
|
-
// Remove empty first/last elements from leading/trailing pipes
|
|
589
|
-
if (parts.length > 0 && parts[0].trim() === '') parts.shift();
|
|
590
|
-
if (parts.length > 0 && parts[parts.length - 1].trim() === '') parts.pop();
|
|
591
|
-
|
|
592
|
-
return parts;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// Pad a separator cell preserving alignment markers (:---:, :---, ---:)
|
|
596
|
-
function padSeparatorCell(cell, width) {
|
|
597
|
-
const trimmed = cell.trim();
|
|
598
|
-
const leftColon = trimmed.startsWith(':');
|
|
599
|
-
const rightColon = trimmed.endsWith(':');
|
|
600
|
-
|
|
601
|
-
// Calculate dash count: width minus colons
|
|
602
|
-
const dashCount = width - (leftColon ? 1 : 0) - (rightColon ? 1 : 0);
|
|
603
|
-
|
|
604
|
-
return (leftColon ? ':' : '') + '-'.repeat(Math.max(1, dashCount)) + (rightColon ? ':' : '');
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// Pad a regular cell to target width
|
|
608
|
-
function padCell(cell, width) {
|
|
609
|
-
const trimmed = cell.trim();
|
|
610
|
-
return trimmed.padEnd(width);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// Identify tables in the document and compute column widths
|
|
614
|
-
function identifyTables(lines, codeBlockRanges) {
|
|
615
|
-
const tables = [];
|
|
616
|
-
|
|
617
|
-
for (let i = 0; i < lines.length - 1; i++) {
|
|
618
|
-
// Skip lines inside code blocks
|
|
619
|
-
if (isInsideCodeBlock(i, codeBlockRanges)) continue;
|
|
620
|
-
|
|
621
|
-
const line = lines[i];
|
|
622
|
-
const nextLine = lines[i + 1];
|
|
623
|
-
|
|
624
|
-
// Look for header + separator pattern
|
|
625
|
-
if (line.includes('|') && isSeparatorRow(nextLine) && !isInsideCodeBlock(i + 1, codeBlockRanges)) {
|
|
626
|
-
const table = { startLine: i, endLine: i + 1, columns: [] };
|
|
627
|
-
|
|
628
|
-
// Extend table to include all following rows with |
|
|
629
|
-
let j = i + 2;
|
|
630
|
-
while (j < lines.length && lines[j].includes('|') && !isInsideCodeBlock(j, codeBlockRanges)) {
|
|
631
|
-
table.endLine = j;
|
|
632
|
-
j++;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
// Compute column widths across all rows
|
|
636
|
-
for (let row = table.startLine; row <= table.endLine; row++) {
|
|
637
|
-
const cells = parseTableCells(lines[row]);
|
|
638
|
-
cells.forEach((cell, col) => {
|
|
639
|
-
const cellWidth = cell.trim().length;
|
|
640
|
-
table.columns[col] = Math.max(table.columns[col] || 0, cellWidth);
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
tables.push(table);
|
|
645
|
-
i = table.endLine; // Skip past this table
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
return tables;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// Build a map: lineNumber -> tableInfo (or undefined)
|
|
653
|
-
function buildTableMap(tables) {
|
|
654
|
-
const map = new Map();
|
|
655
|
-
for (const table of tables) {
|
|
656
|
-
for (let i = table.startLine; i <= table.endLine; i++) {
|
|
657
|
-
map.set(i, table);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
return map;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// Render a table row with padded cells
|
|
664
|
-
function renderTableRow(line, columnWidths, isSeparator) {
|
|
665
|
-
// Preserve original indentation for nested tables (in lists, blockquotes)
|
|
666
|
-
const indent = line.match(/^(\s*)/)[1];
|
|
667
|
-
const trimmedLine = line.trimStart();
|
|
668
|
-
|
|
669
|
-
const cells = parseTableCells(line);
|
|
670
|
-
const hasLeadingPipe = trimmedLine.startsWith('|');
|
|
671
|
-
const hasTrailingPipe = line.trimEnd().endsWith('|');
|
|
672
|
-
|
|
673
|
-
const paddedCells = cells.map((cell, i) => {
|
|
674
|
-
const targetWidth = columnWidths[i] || cell.trim().length;
|
|
675
|
-
if (isSeparator) {
|
|
676
|
-
return padSeparatorCell(cell, targetWidth);
|
|
677
|
-
} else {
|
|
678
|
-
return padCell(cell, targetWidth);
|
|
679
|
-
}
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
// Reconstruct line with pipes, preserving indentation
|
|
683
|
-
let result = indent;
|
|
684
|
-
if (hasLeadingPipe) result += '| ';
|
|
685
|
-
result += paddedCells.join(' | ');
|
|
686
|
-
if (hasTrailingPipe) result += ' |';
|
|
687
|
-
|
|
688
|
-
return result;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
async function saveFile() {
|
|
692
|
-
try {
|
|
693
|
-
const response = await fetch('/save', {
|
|
694
|
-
method: 'POST',
|
|
695
|
-
headers: { 'Content-Type': 'application/json' },
|
|
696
|
-
body: JSON.stringify({
|
|
697
|
-
file: filePath,
|
|
698
|
-
content: fileLines.join('\n')
|
|
699
|
-
})
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
if (!response.ok) {
|
|
703
|
-
console.error('Failed to save:', await response.text());
|
|
704
|
-
}
|
|
705
|
-
} catch (err) {
|
|
706
|
-
console.error('Failed to save:', err.message);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
// Reload file from disk (after external edits)
|
|
711
|
-
function reloadFile() {
|
|
712
|
-
// Warn if there are unsaved changes
|
|
713
|
-
if (hasUnsavedChanges) {
|
|
714
|
-
if (!confirm('You have unsaved changes. Reload anyway?')) {
|
|
715
|
-
return;
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
// Reload the page - server re-reads file on each request
|
|
720
|
-
window.location.reload();
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
// ==================== Edit Mode Functions ====================
|
|
724
|
-
|
|
725
|
-
async function toggleEditMode() {
|
|
726
|
-
const viewMode = document.getElementById('viewMode');
|
|
727
|
-
const editor = document.getElementById('editor');
|
|
728
|
-
const editBtn = document.getElementById('editBtn');
|
|
729
|
-
const saveBtn = document.getElementById('saveBtn');
|
|
730
|
-
const cancelBtn = document.getElementById('cancelBtn');
|
|
731
|
-
|
|
732
|
-
const subtitle = document.querySelector('.subtitle');
|
|
733
|
-
|
|
734
|
-
if (!editMode) {
|
|
735
|
-
// Enter edit mode
|
|
736
|
-
// Capture scroll position from view mode
|
|
737
|
-
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
|
|
738
|
-
|
|
739
|
-
currentContent = fileLines.join('\n');
|
|
740
|
-
// Note: originalContent is set only in init() and after successful saves
|
|
741
|
-
// to ensure Cancel always reverts to the true disk state
|
|
742
|
-
editor.value = currentContent;
|
|
743
|
-
viewMode.style.display = 'none';
|
|
744
|
-
editor.style.display = 'block';
|
|
745
|
-
editBtn.textContent = 'Switch to Annotate';
|
|
746
|
-
subtitle.textContent = 'Edit the file directly by clicking and typing.';
|
|
747
|
-
saveBtn.style.display = 'inline-block';
|
|
748
|
-
cancelBtn.style.display = 'inline-block';
|
|
749
|
-
editMode = true;
|
|
750
|
-
|
|
751
|
-
// Apply scroll position to editor
|
|
752
|
-
editor.scrollTop = scrollTop;
|
|
753
|
-
editor.focus();
|
|
754
|
-
} else {
|
|
755
|
-
// Exit to view mode - auto-save changes per spec
|
|
756
|
-
// Capture scroll position from editor
|
|
757
|
-
const scrollTop = editor.scrollTop;
|
|
758
|
-
|
|
759
|
-
// Auto-save if there are changes (spec requires: "Edit text, click View → changes saved")
|
|
760
|
-
if (hasUnsavedChanges) {
|
|
761
|
-
await saveEdit();
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
currentContent = editor.value;
|
|
765
|
-
fileLines = currentContent.split('\n');
|
|
766
|
-
editor.style.display = 'none';
|
|
767
|
-
viewMode.style.display = 'grid';
|
|
768
|
-
editBtn.textContent = 'Switch to Editing';
|
|
769
|
-
subtitle.textContent = 'Click on a line number to leave an annotation.';
|
|
770
|
-
saveBtn.style.display = 'none';
|
|
771
|
-
cancelBtn.style.display = 'none';
|
|
772
|
-
editMode = false;
|
|
773
|
-
|
|
774
|
-
renderFile();
|
|
775
|
-
updateAnnotationsList();
|
|
776
|
-
|
|
777
|
-
// Apply scroll position to document
|
|
778
|
-
requestAnimationFrame(() => {
|
|
779
|
-
document.documentElement.scrollTop = scrollTop;
|
|
780
|
-
document.body.scrollTop = scrollTop;
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
async function saveEdit() {
|
|
786
|
-
const editor = document.getElementById('editor');
|
|
787
|
-
const content = editor.value;
|
|
788
|
-
|
|
789
|
-
try {
|
|
790
|
-
const response = await fetch('/save', {
|
|
791
|
-
method: 'POST',
|
|
792
|
-
headers: { 'Content-Type': 'application/json' },
|
|
793
|
-
body: JSON.stringify({ file: filePath, content })
|
|
794
|
-
});
|
|
795
|
-
|
|
796
|
-
if (response.ok) {
|
|
797
|
-
currentContent = content;
|
|
798
|
-
originalContent = content;
|
|
799
|
-
fileLines = content.split('\n');
|
|
800
|
-
hasUnsavedChanges = false;
|
|
801
|
-
updateUnsavedIndicator();
|
|
802
|
-
showNotification('File saved!');
|
|
803
|
-
} else {
|
|
804
|
-
const errorText = await response.text();
|
|
805
|
-
throw new Error(errorText || 'Save failed');
|
|
806
|
-
}
|
|
807
|
-
} catch (err) {
|
|
808
|
-
showNotification('Error saving file: ' + err.message, 'error');
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
function cancelEdit() {
|
|
813
|
-
const editor = document.getElementById('editor');
|
|
814
|
-
editor.value = originalContent;
|
|
815
|
-
currentContent = originalContent;
|
|
816
|
-
fileLines = originalContent.split('\n');
|
|
817
|
-
hasUnsavedChanges = false;
|
|
818
|
-
updateUnsavedIndicator();
|
|
819
|
-
toggleEditMode(); // Back to view mode
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
function updateUnsavedIndicator() {
|
|
823
|
-
const indicator = document.getElementById('unsavedIndicator');
|
|
824
|
-
indicator.style.display = hasUnsavedChanges ? 'inline' : 'none';
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
function showNotification(message, type = 'success') {
|
|
828
|
-
// Remove existing notification
|
|
829
|
-
const existing = document.querySelector('.notification');
|
|
830
|
-
if (existing) existing.remove();
|
|
831
|
-
|
|
832
|
-
const notification = document.createElement('div');
|
|
833
|
-
notification.className = 'notification' + (type === 'error' ? ' error' : '');
|
|
834
|
-
notification.textContent = message;
|
|
835
|
-
document.body.appendChild(notification);
|
|
836
|
-
|
|
837
|
-
setTimeout(() => notification.remove(), 3000);
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
// Track unsaved changes in editor
|
|
841
|
-
document.getElementById('editor').addEventListener('input', () => {
|
|
842
|
-
hasUnsavedChanges = document.getElementById('editor').value !== originalContent;
|
|
843
|
-
updateUnsavedIndicator();
|
|
844
|
-
});
|
|
845
|
-
|
|
846
|
-
// Tab key handling for indentation
|
|
847
|
-
document.getElementById('editor').addEventListener('keydown', (e) => {
|
|
848
|
-
if (e.key === 'Tab') {
|
|
849
|
-
e.preventDefault();
|
|
850
|
-
const editor = e.target;
|
|
851
|
-
const start = editor.selectionStart;
|
|
852
|
-
const end = editor.selectionEnd;
|
|
853
|
-
|
|
854
|
-
// Insert 2 spaces (or could use \t)
|
|
855
|
-
editor.value = editor.value.substring(0, start) + ' ' + editor.value.substring(end);
|
|
856
|
-
editor.selectionStart = editor.selectionEnd = start + 2;
|
|
857
|
-
|
|
858
|
-
// Trigger input event to update unsaved indicator
|
|
859
|
-
editor.dispatchEvent(new Event('input'));
|
|
860
|
-
}
|
|
861
|
-
});
|
|
862
|
-
|
|
863
|
-
// Handle keyboard shortcuts
|
|
864
|
-
document.addEventListener('keydown', (e) => {
|
|
865
|
-
if (e.key === 'Escape') {
|
|
866
|
-
closeDialog();
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// Cmd/Ctrl+S to save
|
|
870
|
-
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
|
871
|
-
e.preventDefault();
|
|
872
|
-
if (editMode) {
|
|
873
|
-
saveEdit();
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
// Warn before leaving with unsaved changes
|
|
879
|
-
window.addEventListener('beforeunload', (e) => {
|
|
880
|
-
if (hasUnsavedChanges) {
|
|
881
|
-
e.preventDefault();
|
|
882
|
-
e.returnValue = '';
|
|
883
|
-
}
|
|
884
|
-
});
|
|
885
|
-
|
|
886
|
-
// Triple-enter to submit annotation
|
|
887
|
-
document.getElementById('commentText').addEventListener('keydown', (e) => {
|
|
888
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
889
|
-
consecutiveEnters++;
|
|
890
|
-
if (consecutiveEnters >= 3) {
|
|
891
|
-
e.preventDefault();
|
|
892
|
-
consecutiveEnters = 0;
|
|
893
|
-
saveComment();
|
|
894
|
-
}
|
|
895
|
-
} else {
|
|
896
|
-
consecutiveEnters = 0;
|
|
897
|
-
}
|
|
898
|
-
});
|
|
899
|
-
|
|
900
|
-
// FILE_CONTENT will be injected by the server
|
|
901
|
-
</script>
|
|
902
|
-
</body>
|
|
903
|
-
</html>
|