@nuasite/notes 0.1.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.md +211 -0
- package/dist/overlay.js +1367 -0
- package/package.json +51 -0
- package/src/apply/apply-suggestion.ts +157 -0
- package/src/dev/api-handlers.ts +215 -0
- package/src/dev/middleware.ts +65 -0
- package/src/dev/request-utils.ts +71 -0
- package/src/index.ts +2 -0
- package/src/integration.ts +168 -0
- package/src/overlay/App.tsx +434 -0
- package/src/overlay/components/CommentPopover.tsx +96 -0
- package/src/overlay/components/DiffPreview.tsx +29 -0
- package/src/overlay/components/ElementHighlight.tsx +33 -0
- package/src/overlay/components/SelectionTooltip.tsx +48 -0
- package/src/overlay/components/Sidebar.tsx +70 -0
- package/src/overlay/components/SidebarItem.tsx +104 -0
- package/src/overlay/components/StaleWarning.tsx +19 -0
- package/src/overlay/components/SuggestPopover.tsx +139 -0
- package/src/overlay/components/Toolbar.tsx +38 -0
- package/src/overlay/env.d.ts +4 -0
- package/src/overlay/index.tsx +71 -0
- package/src/overlay/lib/cms-bridge.ts +33 -0
- package/src/overlay/lib/dom-walker.ts +61 -0
- package/src/overlay/lib/manifest-fetch.ts +35 -0
- package/src/overlay/lib/notes-fetch.ts +121 -0
- package/src/overlay/lib/range-anchor.ts +87 -0
- package/src/overlay/lib/url-mode.ts +43 -0
- package/src/overlay/styles.css +526 -0
- package/src/overlay/types.ts +66 -0
- package/src/storage/id-gen.ts +32 -0
- package/src/storage/json-store.ts +196 -0
- package/src/storage/slug.ts +35 -0
- package/src/storage/types.ts +100 -0
- package/src/tsconfig.json +6 -0
- package/src/types.ts +50 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nuasite/notes overlay styles.
|
|
3
|
+
*
|
|
4
|
+
* Imported via Vite's `?inline` query and injected into a shadow root, so
|
|
5
|
+
* these styles never leak into the host page (and the host page can't
|
|
6
|
+
* accidentally style notes UI). Variables on `:host` are the only knobs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
:host {
|
|
10
|
+
--notes-bg: #ffffff;
|
|
11
|
+
--notes-fg: #0f172a;
|
|
12
|
+
--notes-muted: #64748b;
|
|
13
|
+
--notes-border: #e2e8f0;
|
|
14
|
+
--notes-accent: #f59e0b;
|
|
15
|
+
--notes-accent-fg: #1f2937;
|
|
16
|
+
--notes-danger: #dc2626;
|
|
17
|
+
--notes-success: #16a34a;
|
|
18
|
+
--notes-shadow: 0 10px 30px rgba(15, 23, 42, 0.18);
|
|
19
|
+
--notes-radius: 10px;
|
|
20
|
+
--notes-sidebar-w: 360px;
|
|
21
|
+
--notes-z: 2147483600;
|
|
22
|
+
|
|
23
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
24
|
+
font-size: 14px;
|
|
25
|
+
line-height: 1.45;
|
|
26
|
+
color: var(--notes-fg);
|
|
27
|
+
-webkit-font-smoothing: antialiased;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
* {
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
button {
|
|
35
|
+
font: inherit;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
border: none;
|
|
38
|
+
background: none;
|
|
39
|
+
color: inherit;
|
|
40
|
+
padding: 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
input,
|
|
44
|
+
textarea {
|
|
45
|
+
font: inherit;
|
|
46
|
+
color: inherit;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.notes-root {
|
|
50
|
+
position: fixed;
|
|
51
|
+
inset: 0;
|
|
52
|
+
pointer-events: none;
|
|
53
|
+
z-index: var(--notes-z);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* Toolbar (top, full width) */
|
|
57
|
+
.notes-toolbar {
|
|
58
|
+
position: fixed;
|
|
59
|
+
top: 0;
|
|
60
|
+
left: 0;
|
|
61
|
+
right: 0;
|
|
62
|
+
height: 44px;
|
|
63
|
+
background: var(--notes-bg);
|
|
64
|
+
border-bottom: 1px solid var(--notes-border);
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
justify-content: space-between;
|
|
68
|
+
padding: 0 16px;
|
|
69
|
+
box-shadow: var(--notes-shadow);
|
|
70
|
+
pointer-events: auto;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.notes-toolbar__brand {
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
gap: 8px;
|
|
77
|
+
font-weight: 600;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.notes-toolbar__dot {
|
|
81
|
+
width: 8px;
|
|
82
|
+
height: 8px;
|
|
83
|
+
border-radius: 50%;
|
|
84
|
+
background: var(--notes-accent);
|
|
85
|
+
box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.2);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.notes-toolbar__page {
|
|
89
|
+
color: var(--notes-muted);
|
|
90
|
+
font-weight: 400;
|
|
91
|
+
margin-left: 12px;
|
|
92
|
+
font-size: 13px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.notes-toolbar__actions {
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
gap: 8px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.notes-btn {
|
|
102
|
+
padding: 6px 12px;
|
|
103
|
+
border-radius: 6px;
|
|
104
|
+
background: #f1f5f9;
|
|
105
|
+
color: var(--notes-fg);
|
|
106
|
+
font-size: 13px;
|
|
107
|
+
font-weight: 500;
|
|
108
|
+
transition: background 0.15s;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.notes-btn:hover {
|
|
112
|
+
background: #e2e8f0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.notes-btn--primary {
|
|
116
|
+
background: var(--notes-accent);
|
|
117
|
+
color: var(--notes-accent-fg);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.notes-btn--primary:hover {
|
|
121
|
+
background: #d97706;
|
|
122
|
+
color: #ffffff;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.notes-btn--ghost {
|
|
126
|
+
background: transparent;
|
|
127
|
+
color: var(--notes-muted);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.notes-btn--ghost:hover {
|
|
131
|
+
color: var(--notes-fg);
|
|
132
|
+
background: #f1f5f9;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.notes-btn--danger {
|
|
136
|
+
color: var(--notes-danger);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Sidebar (right side) */
|
|
140
|
+
.notes-sidebar {
|
|
141
|
+
position: fixed;
|
|
142
|
+
top: 44px;
|
|
143
|
+
right: 0;
|
|
144
|
+
bottom: 0;
|
|
145
|
+
width: var(--notes-sidebar-w);
|
|
146
|
+
background: var(--notes-bg);
|
|
147
|
+
border-left: 1px solid var(--notes-border);
|
|
148
|
+
box-shadow: var(--notes-shadow);
|
|
149
|
+
display: flex;
|
|
150
|
+
flex-direction: column;
|
|
151
|
+
pointer-events: auto;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.notes-sidebar__header {
|
|
155
|
+
padding: 16px 16px 12px;
|
|
156
|
+
border-bottom: 1px solid var(--notes-border);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.notes-sidebar__title {
|
|
160
|
+
font-size: 16px;
|
|
161
|
+
font-weight: 600;
|
|
162
|
+
margin: 0 0 4px;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.notes-sidebar__meta {
|
|
166
|
+
font-size: 12px;
|
|
167
|
+
color: var(--notes-muted);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.notes-sidebar__list {
|
|
171
|
+
flex: 1;
|
|
172
|
+
overflow-y: auto;
|
|
173
|
+
padding: 12px;
|
|
174
|
+
display: flex;
|
|
175
|
+
flex-direction: column;
|
|
176
|
+
gap: 10px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.notes-sidebar__empty {
|
|
180
|
+
padding: 24px 16px;
|
|
181
|
+
text-align: center;
|
|
182
|
+
color: var(--notes-muted);
|
|
183
|
+
font-size: 13px;
|
|
184
|
+
line-height: 1.5;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.notes-sidebar__hint {
|
|
188
|
+
font-size: 12px;
|
|
189
|
+
color: var(--notes-muted);
|
|
190
|
+
margin-top: 6px;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* Item card */
|
|
194
|
+
.notes-item {
|
|
195
|
+
border: 1px solid var(--notes-border);
|
|
196
|
+
border-radius: var(--notes-radius);
|
|
197
|
+
padding: 12px;
|
|
198
|
+
background: #ffffff;
|
|
199
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.notes-item:hover {
|
|
203
|
+
border-color: #cbd5e1;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.notes-item--active {
|
|
207
|
+
border-color: var(--notes-accent);
|
|
208
|
+
box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.12);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.notes-item--resolved {
|
|
212
|
+
opacity: 0.55;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.notes-item__head {
|
|
216
|
+
display: flex;
|
|
217
|
+
align-items: center;
|
|
218
|
+
justify-content: space-between;
|
|
219
|
+
margin-bottom: 6px;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.notes-item__author {
|
|
223
|
+
font-weight: 600;
|
|
224
|
+
font-size: 13px;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.notes-item__time {
|
|
228
|
+
font-size: 11px;
|
|
229
|
+
color: var(--notes-muted);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.notes-item__snippet {
|
|
233
|
+
font-size: 12px;
|
|
234
|
+
color: var(--notes-muted);
|
|
235
|
+
background: #f8fafc;
|
|
236
|
+
border-left: 3px solid var(--notes-border);
|
|
237
|
+
padding: 6px 8px;
|
|
238
|
+
margin-bottom: 6px;
|
|
239
|
+
border-radius: 0 4px 4px 0;
|
|
240
|
+
white-space: pre-wrap;
|
|
241
|
+
word-break: break-word;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.notes-item__body {
|
|
245
|
+
font-size: 13px;
|
|
246
|
+
white-space: pre-wrap;
|
|
247
|
+
word-break: break-word;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.notes-item__actions {
|
|
251
|
+
display: flex;
|
|
252
|
+
gap: 6px;
|
|
253
|
+
margin-top: 10px;
|
|
254
|
+
padding-top: 10px;
|
|
255
|
+
border-top: 1px solid #f1f5f9;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.notes-item__badge {
|
|
259
|
+
display: inline-block;
|
|
260
|
+
font-size: 10px;
|
|
261
|
+
font-weight: 600;
|
|
262
|
+
text-transform: uppercase;
|
|
263
|
+
letter-spacing: 0.04em;
|
|
264
|
+
padding: 2px 6px;
|
|
265
|
+
border-radius: 4px;
|
|
266
|
+
margin-right: 6px;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.notes-item__badge--comment {
|
|
270
|
+
background: #dbeafe;
|
|
271
|
+
color: #1e40af;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.notes-item__badge--suggestion {
|
|
275
|
+
background: #fef3c7;
|
|
276
|
+
color: #92400e;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.notes-item__badge--resolved {
|
|
280
|
+
background: #dcfce7;
|
|
281
|
+
color: #166534;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/* Element highlight ring */
|
|
285
|
+
.notes-highlight {
|
|
286
|
+
position: fixed;
|
|
287
|
+
pointer-events: none;
|
|
288
|
+
border: 2px solid var(--notes-accent);
|
|
289
|
+
border-radius: 4px;
|
|
290
|
+
box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.18);
|
|
291
|
+
transition: all 0.08s ease-out;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.notes-highlight--persistent {
|
|
295
|
+
border-color: rgba(245, 158, 11, 0.6);
|
|
296
|
+
box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.12);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* Comment popover */
|
|
300
|
+
.notes-popover {
|
|
301
|
+
position: fixed;
|
|
302
|
+
width: 320px;
|
|
303
|
+
background: var(--notes-bg);
|
|
304
|
+
border: 1px solid var(--notes-border);
|
|
305
|
+
border-radius: var(--notes-radius);
|
|
306
|
+
box-shadow: var(--notes-shadow);
|
|
307
|
+
padding: 14px;
|
|
308
|
+
pointer-events: auto;
|
|
309
|
+
display: flex;
|
|
310
|
+
flex-direction: column;
|
|
311
|
+
gap: 10px;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.notes-popover__title {
|
|
315
|
+
font-size: 13px;
|
|
316
|
+
font-weight: 600;
|
|
317
|
+
margin: 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.notes-popover__snippet {
|
|
321
|
+
font-size: 12px;
|
|
322
|
+
color: var(--notes-muted);
|
|
323
|
+
background: #f8fafc;
|
|
324
|
+
border-left: 3px solid var(--notes-border);
|
|
325
|
+
padding: 6px 8px;
|
|
326
|
+
border-radius: 0 4px 4px 0;
|
|
327
|
+
max-height: 60px;
|
|
328
|
+
overflow: hidden;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.notes-popover textarea {
|
|
332
|
+
width: 100%;
|
|
333
|
+
min-height: 80px;
|
|
334
|
+
resize: vertical;
|
|
335
|
+
border: 1px solid var(--notes-border);
|
|
336
|
+
border-radius: 6px;
|
|
337
|
+
padding: 8px 10px;
|
|
338
|
+
background: #ffffff;
|
|
339
|
+
outline: none;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.notes-popover textarea:focus {
|
|
343
|
+
border-color: var(--notes-accent);
|
|
344
|
+
box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.15);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.notes-popover input[type='text'] {
|
|
348
|
+
border: 1px solid var(--notes-border);
|
|
349
|
+
border-radius: 6px;
|
|
350
|
+
padding: 6px 10px;
|
|
351
|
+
background: #ffffff;
|
|
352
|
+
outline: none;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.notes-popover input[type='text']:focus {
|
|
356
|
+
border-color: var(--notes-accent);
|
|
357
|
+
box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.15);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.notes-popover__row {
|
|
361
|
+
display: flex;
|
|
362
|
+
justify-content: space-between;
|
|
363
|
+
align-items: center;
|
|
364
|
+
gap: 8px;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* Selection tooltip */
|
|
368
|
+
.notes-selection-tooltip {
|
|
369
|
+
position: fixed;
|
|
370
|
+
display: flex;
|
|
371
|
+
align-items: center;
|
|
372
|
+
gap: 6px;
|
|
373
|
+
padding: 4px 6px;
|
|
374
|
+
background: #0f172a;
|
|
375
|
+
color: #ffffff;
|
|
376
|
+
border-radius: 8px;
|
|
377
|
+
box-shadow: var(--notes-shadow);
|
|
378
|
+
pointer-events: auto;
|
|
379
|
+
z-index: calc(var(--notes-z) + 5);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.notes-selection-tooltip .notes-btn {
|
|
383
|
+
background: rgba(255, 255, 255, 0.08);
|
|
384
|
+
color: #ffffff;
|
|
385
|
+
font-size: 12px;
|
|
386
|
+
padding: 4px 8px;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.notes-selection-tooltip .notes-btn--primary {
|
|
390
|
+
background: var(--notes-accent);
|
|
391
|
+
color: var(--notes-accent-fg);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.notes-selection-tooltip .notes-btn--ghost {
|
|
395
|
+
color: #e2e8f0;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.notes-selection-tooltip .notes-btn--ghost:hover {
|
|
399
|
+
background: rgba(255, 255, 255, 0.16);
|
|
400
|
+
color: #ffffff;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/* Suggest popover variations */
|
|
404
|
+
.notes-popover--suggest {
|
|
405
|
+
width: 360px;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.notes-popover__label {
|
|
409
|
+
display: block;
|
|
410
|
+
font-size: 11px;
|
|
411
|
+
font-weight: 600;
|
|
412
|
+
text-transform: uppercase;
|
|
413
|
+
letter-spacing: 0.04em;
|
|
414
|
+
color: var(--notes-muted);
|
|
415
|
+
margin-bottom: 4px;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.notes-popover__original {
|
|
419
|
+
background: #fef3c7;
|
|
420
|
+
border: 1px solid #fde68a;
|
|
421
|
+
border-radius: 6px;
|
|
422
|
+
padding: 8px 10px;
|
|
423
|
+
font-size: 13px;
|
|
424
|
+
color: #78350f;
|
|
425
|
+
max-height: 80px;
|
|
426
|
+
overflow-y: auto;
|
|
427
|
+
white-space: pre-wrap;
|
|
428
|
+
word-break: break-word;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.notes-strikethrough {
|
|
432
|
+
text-decoration: line-through;
|
|
433
|
+
text-decoration-color: rgba(220, 38, 38, 0.6);
|
|
434
|
+
text-decoration-thickness: 2px;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/* Diff preview in sidebar */
|
|
438
|
+
.notes-diff {
|
|
439
|
+
font-size: 13px;
|
|
440
|
+
border-radius: 6px;
|
|
441
|
+
background: #f8fafc;
|
|
442
|
+
border: 1px solid var(--notes-border);
|
|
443
|
+
overflow: hidden;
|
|
444
|
+
margin-bottom: 8px;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.notes-diff__row {
|
|
448
|
+
padding: 6px 10px;
|
|
449
|
+
display: flex;
|
|
450
|
+
gap: 6px;
|
|
451
|
+
white-space: pre-wrap;
|
|
452
|
+
word-break: break-word;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.notes-diff__row--del {
|
|
456
|
+
background: #fef2f2;
|
|
457
|
+
color: #7f1d1d;
|
|
458
|
+
border-bottom: 1px solid #fee2e2;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.notes-diff__row--ins {
|
|
462
|
+
background: #f0fdf4;
|
|
463
|
+
color: #14532d;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.notes-diff__marker {
|
|
467
|
+
font-weight: 700;
|
|
468
|
+
font-family: ui-monospace, SFMono-Regular, monospace;
|
|
469
|
+
width: 12px;
|
|
470
|
+
flex-shrink: 0;
|
|
471
|
+
text-align: center;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.notes-item__rationale {
|
|
475
|
+
font-size: 12px;
|
|
476
|
+
color: var(--notes-muted);
|
|
477
|
+
margin-bottom: 6px;
|
|
478
|
+
font-style: italic;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.notes-item__rationale-label {
|
|
482
|
+
font-style: normal;
|
|
483
|
+
font-weight: 600;
|
|
484
|
+
color: var(--notes-fg);
|
|
485
|
+
margin-right: 4px;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/* Stale warning */
|
|
489
|
+
.notes-stale {
|
|
490
|
+
display: flex;
|
|
491
|
+
align-items: center;
|
|
492
|
+
gap: 6px;
|
|
493
|
+
background: #fef3c7;
|
|
494
|
+
border: 1px solid #fde68a;
|
|
495
|
+
color: #92400e;
|
|
496
|
+
font-size: 12px;
|
|
497
|
+
padding: 6px 8px;
|
|
498
|
+
border-radius: 6px;
|
|
499
|
+
margin-bottom: 8px;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.notes-stale__icon {
|
|
503
|
+
font-size: 14px;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/* Persistent highlight variant for suggestion ranges */
|
|
507
|
+
.notes-highlight--suggestion {
|
|
508
|
+
border-color: rgba(250, 204, 21, 0.9);
|
|
509
|
+
box-shadow: 0 0 0 3px rgba(250, 204, 21, 0.18);
|
|
510
|
+
background: rgba(254, 243, 199, 0.25);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/* Banner shown when notes API fails to load */
|
|
514
|
+
.notes-banner {
|
|
515
|
+
position: fixed;
|
|
516
|
+
top: 56px;
|
|
517
|
+
left: 16px;
|
|
518
|
+
right: calc(var(--notes-sidebar-w) + 16px);
|
|
519
|
+
padding: 10px 14px;
|
|
520
|
+
background: #fef2f2;
|
|
521
|
+
border: 1px solid #fecaca;
|
|
522
|
+
color: #991b1b;
|
|
523
|
+
border-radius: var(--notes-radius);
|
|
524
|
+
font-size: 13px;
|
|
525
|
+
pointer-events: auto;
|
|
526
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side types used by the Preact overlay.
|
|
3
|
+
*
|
|
4
|
+
* The overlay only knows what comes back from the dev API, so we duplicate
|
|
5
|
+
* the storage types here in their JSON-friendly form (no Date objects, no
|
|
6
|
+
* file handles). Keeping a separate copy lets the overlay bundle ship
|
|
7
|
+
* without pulling in node-only modules through `src/storage`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type NoteType = 'comment' | 'suggestion'
|
|
11
|
+
export type NoteStatus = 'open' | 'resolved' | 'applied' | 'rejected' | 'stale'
|
|
12
|
+
|
|
13
|
+
export interface NoteRange {
|
|
14
|
+
anchorText: string
|
|
15
|
+
originalText: string
|
|
16
|
+
suggestedText: string
|
|
17
|
+
rationale?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface NoteReply {
|
|
21
|
+
id: string
|
|
22
|
+
author: string
|
|
23
|
+
body: string
|
|
24
|
+
createdAt: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface NoteItem {
|
|
28
|
+
id: string
|
|
29
|
+
type: NoteType
|
|
30
|
+
targetCmsId: string
|
|
31
|
+
targetSourcePath?: string
|
|
32
|
+
targetSourceLine?: number
|
|
33
|
+
targetSnippet?: string
|
|
34
|
+
range: NoteRange | null
|
|
35
|
+
body: string
|
|
36
|
+
author: string
|
|
37
|
+
createdAt: string
|
|
38
|
+
updatedAt?: string
|
|
39
|
+
status: NoteStatus
|
|
40
|
+
replies: NoteReply[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface NotesPageFile {
|
|
44
|
+
page: string
|
|
45
|
+
lastUpdated: string
|
|
46
|
+
items: NoteItem[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Lightweight subset of the CMS manifest entry the overlay needs. */
|
|
50
|
+
export interface CmsManifestEntry {
|
|
51
|
+
tag?: string
|
|
52
|
+
text?: string
|
|
53
|
+
sourcePath?: string
|
|
54
|
+
sourceLine?: number
|
|
55
|
+
sourceSnippet?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface CmsPageManifest {
|
|
59
|
+
page: string
|
|
60
|
+
entries?: Record<string, CmsManifestEntry>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Author info — Phase 2 reads it from localStorage with a default. */
|
|
64
|
+
export interface NotesAuthor {
|
|
65
|
+
name: string
|
|
66
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ID generators for notes and replies.
|
|
3
|
+
*
|
|
4
|
+
* Format: `n-YYYY-MM-DD-xxxxxx` for items and `r-YYYY-MM-DD-xxxxxx` for replies.
|
|
5
|
+
* The date prefix makes JSON files trivially scannable in a text editor; the
|
|
6
|
+
* 6-character random suffix is enough for collision-free local-first usage.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const ALPHABET = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
|
10
|
+
|
|
11
|
+
function todayIsoDate(now: Date = new Date()): string {
|
|
12
|
+
const y = now.getUTCFullYear()
|
|
13
|
+
const m = String(now.getUTCMonth() + 1).padStart(2, '0')
|
|
14
|
+
const d = String(now.getUTCDate()).padStart(2, '0')
|
|
15
|
+
return `${y}-${m}-${d}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function randomSuffix(length = 6): string {
|
|
19
|
+
let out = ''
|
|
20
|
+
for (let i = 0; i < length; i++) {
|
|
21
|
+
out += ALPHABET[Math.floor(Math.random() * ALPHABET.length)]
|
|
22
|
+
}
|
|
23
|
+
return out
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function generateNoteId(now: Date = new Date()): string {
|
|
27
|
+
return `n-${todayIsoDate(now)}-${randomSuffix()}`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function generateReplyId(now: Date = new Date()): string {
|
|
31
|
+
return `r-${todayIsoDate(now)}-${randomSuffix()}`
|
|
32
|
+
}
|