@wipcomputer/markdown-viewer 1.0.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/CLAUDE.md +138 -0
- package/LICENSE +21 -0
- package/README.md +136 -0
- package/bbedit-preview-template.html +559 -0
- package/demo/demo-image.png +0 -0
- package/demo/demo.md +165 -0
- package/images/01.png +0 -0
- package/images/02.png +0 -0
- package/images/03.png +0 -0
- package/markdown-viewer.html +1440 -0
- package/package.json +22 -0
- package/server.js +321 -0
|
@@ -0,0 +1,1440 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>WIP Computer: Live .md Viewer</title>
|
|
7
|
+
<!-- Markdown parser -->
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
9
|
+
<!-- Syntax highlighting -->
|
|
10
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css" id="highlight-theme">
|
|
11
|
+
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
|
|
12
|
+
<!-- Mermaid for diagrams -->
|
|
13
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
|
14
|
+
<!-- KaTeX for math -->
|
|
15
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
|
|
16
|
+
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
|
17
|
+
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
|
|
18
|
+
<style>
|
|
19
|
+
:root {
|
|
20
|
+
--bg-primary: #fcfbf9;
|
|
21
|
+
--bg-secondary: #f6f5f3;
|
|
22
|
+
--bg-code: #f6f5f3;
|
|
23
|
+
--text-primary: #2c2926;
|
|
24
|
+
--text-secondary: #888;
|
|
25
|
+
--text-heading: #1a1714;
|
|
26
|
+
--border-color: #e8e7e5;
|
|
27
|
+
--accent-color: #2c2926;
|
|
28
|
+
--link-color: #1a1714;
|
|
29
|
+
--table-stripe: #faf9f7;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
[data-style="github"] {
|
|
33
|
+
--bg-primary: #ffffff;
|
|
34
|
+
--bg-secondary: #f6f8fa;
|
|
35
|
+
--bg-code: #f6f8fa;
|
|
36
|
+
--text-primary: #1f2328;
|
|
37
|
+
--text-secondary: #656d76;
|
|
38
|
+
--text-heading: #1f2328;
|
|
39
|
+
--border-color: #d1d9e0;
|
|
40
|
+
--accent-color: #1f2328;
|
|
41
|
+
--link-color: #0969da;
|
|
42
|
+
--table-stripe: #f6f8fa;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
[data-style="github"][data-theme="dark"] {
|
|
46
|
+
--bg-primary: #0d1117;
|
|
47
|
+
--bg-secondary: #161b22;
|
|
48
|
+
--bg-code: #161b22;
|
|
49
|
+
--text-primary: #e6edf3;
|
|
50
|
+
--text-secondary: #8d96a0;
|
|
51
|
+
--text-heading: #e6edf3;
|
|
52
|
+
--border-color: #30363d;
|
|
53
|
+
--accent-color: #e6edf3;
|
|
54
|
+
--link-color: #58a6ff;
|
|
55
|
+
--table-stripe: #161b22;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
[data-style="github"] #markdown-content h1 {
|
|
59
|
+
font-size: 2em;
|
|
60
|
+
letter-spacing: normal;
|
|
61
|
+
padding-bottom: 0.3em;
|
|
62
|
+
border-bottom: 1px solid var(--border-color);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
[data-style="github"] #markdown-content h2 {
|
|
66
|
+
font-size: 1.5em;
|
|
67
|
+
letter-spacing: normal;
|
|
68
|
+
padding-bottom: 0.3em;
|
|
69
|
+
border-bottom: 1px solid var(--border-color);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
[data-style="github"] #markdown-content h3 {
|
|
73
|
+
font-size: 1.25em;
|
|
74
|
+
letter-spacing: normal;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
[data-style="github"] #markdown-content h4 {
|
|
78
|
+
font-size: 1em;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
[data-style="github"] #markdown-content {
|
|
82
|
+
font-size: 16px;
|
|
83
|
+
line-height: 1.5;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
[data-style="github"] #markdown-content a {
|
|
87
|
+
color: var(--link-color);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
[data-theme="dark"] {
|
|
91
|
+
--bg-primary: #111;
|
|
92
|
+
--bg-secondary: #0a0a0a;
|
|
93
|
+
--bg-code: #1a1a1a;
|
|
94
|
+
--text-primary: #e0e0e0;
|
|
95
|
+
--text-secondary: #888;
|
|
96
|
+
--text-heading: #fff;
|
|
97
|
+
--border-color: #2a2a2a;
|
|
98
|
+
--accent-color: #ccc;
|
|
99
|
+
--link-color: #ddd;
|
|
100
|
+
--table-stripe: #151515;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
* {
|
|
104
|
+
margin: 0;
|
|
105
|
+
padding: 0;
|
|
106
|
+
box-sizing: border-box;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
body {
|
|
110
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
|
|
111
|
+
line-height: 1.6;
|
|
112
|
+
color: var(--text-primary);
|
|
113
|
+
background: var(--bg-secondary);
|
|
114
|
+
height: 100vh;
|
|
115
|
+
overflow: hidden;
|
|
116
|
+
transition: background 0.2s ease, color 0.2s ease;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@media (prefers-reduced-motion: reduce) {
|
|
120
|
+
*, *::before, *::after {
|
|
121
|
+
transition-duration: 0.01ms !important;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#drop-zone {
|
|
126
|
+
position: fixed;
|
|
127
|
+
top: 0;
|
|
128
|
+
left: 0;
|
|
129
|
+
right: 0;
|
|
130
|
+
bottom: 0;
|
|
131
|
+
display: flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
justify-content: center;
|
|
134
|
+
background: var(--bg-secondary);
|
|
135
|
+
transition: all 0.2s ease;
|
|
136
|
+
z-index: 1000;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#drop-zone.hidden {
|
|
140
|
+
display: none;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#drop-zone.dragover {
|
|
144
|
+
background: var(--border-color);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#drop-zone.dragover .drop-border {
|
|
148
|
+
border-color: var(--text-primary);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.drop-message {
|
|
152
|
+
text-align: center;
|
|
153
|
+
color: var(--text-primary);
|
|
154
|
+
pointer-events: none;
|
|
155
|
+
max-width: 440px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.drop-message .brand {
|
|
159
|
+
font-size: 0.7em;
|
|
160
|
+
font-weight: 600;
|
|
161
|
+
letter-spacing: 0.18em;
|
|
162
|
+
text-transform: uppercase;
|
|
163
|
+
color: var(--text-secondary);
|
|
164
|
+
margin-bottom: 16px;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.drop-message h1 {
|
|
168
|
+
font-size: clamp(2em, 5vw, 2.8em);
|
|
169
|
+
margin-bottom: 8px;
|
|
170
|
+
font-weight: 700;
|
|
171
|
+
letter-spacing: -0.03em;
|
|
172
|
+
color: var(--text-heading);
|
|
173
|
+
line-height: 1.1;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.drop-message .tagline {
|
|
177
|
+
font-size: 1.05em;
|
|
178
|
+
color: var(--text-secondary);
|
|
179
|
+
margin-bottom: 48px;
|
|
180
|
+
line-height: 1.6;
|
|
181
|
+
font-weight: 400;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.drop-border {
|
|
185
|
+
pointer-events: auto;
|
|
186
|
+
border: 1.5px dashed var(--border-color);
|
|
187
|
+
border-radius: 8px;
|
|
188
|
+
padding: 48px 40px;
|
|
189
|
+
transition: border-color 0.2s ease;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.drop-border:hover {
|
|
193
|
+
border-color: var(--text-secondary);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.drop-message button {
|
|
197
|
+
pointer-events: auto;
|
|
198
|
+
background: var(--text-primary);
|
|
199
|
+
color: var(--bg-primary);
|
|
200
|
+
border: none;
|
|
201
|
+
padding: 12px 32px;
|
|
202
|
+
border-radius: 8px;
|
|
203
|
+
font-size: 0.95em;
|
|
204
|
+
font-weight: 600;
|
|
205
|
+
cursor: pointer;
|
|
206
|
+
transition: all 0.2s ease;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.drop-message button:hover {
|
|
210
|
+
background: var(--text-heading);
|
|
211
|
+
transform: translateY(-1px);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.drop-message button:active {
|
|
215
|
+
transform: translateY(0);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.drop-hint {
|
|
219
|
+
font-size: 0.8em;
|
|
220
|
+
color: var(--text-secondary);
|
|
221
|
+
margin-top: 16px;
|
|
222
|
+
font-weight: 400;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#viewer-container {
|
|
226
|
+
height: 100vh;
|
|
227
|
+
display: flex;
|
|
228
|
+
flex-direction: column;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
#header {
|
|
232
|
+
background: var(--bg-primary);
|
|
233
|
+
color: var(--text-primary);
|
|
234
|
+
padding: 12px 20px;
|
|
235
|
+
border-bottom: 1px solid var(--border-color);
|
|
236
|
+
display: flex;
|
|
237
|
+
justify-content: space-between;
|
|
238
|
+
align-items: center;
|
|
239
|
+
min-height: 52px;
|
|
240
|
+
flex-wrap: wrap;
|
|
241
|
+
gap: 10px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
#file-info {
|
|
245
|
+
display: flex;
|
|
246
|
+
align-items: center;
|
|
247
|
+
gap: 16px;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#file-name {
|
|
251
|
+
font-size: 1em;
|
|
252
|
+
font-weight: 600;
|
|
253
|
+
color: var(--text-heading);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#live-status {
|
|
257
|
+
display: inline-flex;
|
|
258
|
+
align-items: center;
|
|
259
|
+
gap: 6px;
|
|
260
|
+
font-size: 0.75em;
|
|
261
|
+
font-weight: 500;
|
|
262
|
+
padding: 2px 8px;
|
|
263
|
+
border-radius: 12px;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
#live-status .dot {
|
|
267
|
+
width: 7px;
|
|
268
|
+
height: 7px;
|
|
269
|
+
border-radius: 50%;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
#live-status.live { color: #2a7d3f; }
|
|
273
|
+
#live-status.live .dot { background: #34c759; }
|
|
274
|
+
#live-status.static { color: #a0724e; }
|
|
275
|
+
#live-status.static .dot { background: #e89a3c; }
|
|
276
|
+
|
|
277
|
+
.dark-mode #live-status.live { color: #5dd879; }
|
|
278
|
+
.dark-mode #live-status.static { color: #e8a654; }
|
|
279
|
+
|
|
280
|
+
#folder-warning, #theme-dropdown, #theme-prompt-modal {
|
|
281
|
+
position: absolute;
|
|
282
|
+
background: var(--bg-primary);
|
|
283
|
+
border: 1px solid var(--border-color);
|
|
284
|
+
border-radius: 8px;
|
|
285
|
+
font-size: 0.8em;
|
|
286
|
+
color: var(--text-primary);
|
|
287
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
|
288
|
+
z-index: 100;
|
|
289
|
+
display: none;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
#folder-warning {
|
|
293
|
+
top: 48px;
|
|
294
|
+
right: 80px;
|
|
295
|
+
padding: 12px 16px;
|
|
296
|
+
max-width: 260px;
|
|
297
|
+
line-height: 1.5;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
#folder-warning.show { display: block; }
|
|
301
|
+
|
|
302
|
+
#theme-dropdown {
|
|
303
|
+
top: 48px;
|
|
304
|
+
right: 140px;
|
|
305
|
+
padding: 4px 0;
|
|
306
|
+
min-width: 160px;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
#theme-dropdown.show { display: block; }
|
|
310
|
+
|
|
311
|
+
#theme-dropdown .theme-option {
|
|
312
|
+
display: flex;
|
|
313
|
+
align-items: center;
|
|
314
|
+
gap: 8px;
|
|
315
|
+
padding: 8px 14px;
|
|
316
|
+
cursor: pointer;
|
|
317
|
+
transition: background 0.2s ease;
|
|
318
|
+
border: none;
|
|
319
|
+
background: none;
|
|
320
|
+
width: 100%;
|
|
321
|
+
text-align: left;
|
|
322
|
+
font-size: 0.85em;
|
|
323
|
+
color: var(--text-primary);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
#theme-dropdown .theme-option:hover {
|
|
327
|
+
background: var(--bg-secondary);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
#theme-dropdown .theme-option.active {
|
|
331
|
+
font-weight: 600;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
#theme-dropdown .theme-option .check {
|
|
335
|
+
width: 14px;
|
|
336
|
+
font-size: 0.9em;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
#theme-dropdown .divider {
|
|
340
|
+
height: 1px;
|
|
341
|
+
background: var(--border-color);
|
|
342
|
+
margin: 4px 0;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
#theme-prompt-modal {
|
|
346
|
+
position: fixed;
|
|
347
|
+
top: 50%;
|
|
348
|
+
left: 50%;
|
|
349
|
+
transform: translate(-50%, -50%);
|
|
350
|
+
padding: 24px 28px;
|
|
351
|
+
max-width: 420px;
|
|
352
|
+
width: 90%;
|
|
353
|
+
line-height: 1.6;
|
|
354
|
+
border-radius: 12px;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
#theme-prompt-modal.show { display: block; }
|
|
358
|
+
|
|
359
|
+
#theme-prompt-modal h3 {
|
|
360
|
+
margin-bottom: 12px;
|
|
361
|
+
font-size: 1em;
|
|
362
|
+
color: var(--text-heading);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
#theme-prompt-modal .prompt-text {
|
|
366
|
+
background: var(--bg-secondary);
|
|
367
|
+
border: 1px solid var(--border-color);
|
|
368
|
+
border-radius: 6px;
|
|
369
|
+
padding: 12px;
|
|
370
|
+
font-family: monospace;
|
|
371
|
+
font-size: 0.85em;
|
|
372
|
+
line-height: 1.5;
|
|
373
|
+
margin-bottom: 16px;
|
|
374
|
+
cursor: pointer;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
#theme-prompt-modal .prompt-text:hover {
|
|
378
|
+
border-color: var(--text-secondary);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
#theme-prompt-modal .modal-actions {
|
|
382
|
+
display: flex;
|
|
383
|
+
justify-content: flex-end;
|
|
384
|
+
gap: 8px;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
#theme-prompt-modal button {
|
|
388
|
+
padding: 6px 14px;
|
|
389
|
+
border-radius: 6px;
|
|
390
|
+
border: 1px solid var(--border-color);
|
|
391
|
+
background: var(--bg-primary);
|
|
392
|
+
color: var(--text-primary);
|
|
393
|
+
cursor: pointer;
|
|
394
|
+
font-size: 0.85em;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
#theme-prompt-modal button.primary {
|
|
398
|
+
background: var(--text-heading);
|
|
399
|
+
color: var(--bg-primary);
|
|
400
|
+
border-color: var(--text-heading);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
#theme-modal-overlay {
|
|
404
|
+
position: fixed;
|
|
405
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
406
|
+
background: rgba(0,0,0,0.3);
|
|
407
|
+
z-index: 99;
|
|
408
|
+
display: none;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
#theme-modal-overlay.show { display: block; }
|
|
412
|
+
|
|
413
|
+
#last-updated {
|
|
414
|
+
font-size: 0.8em;
|
|
415
|
+
color: var(--text-secondary);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
#controls {
|
|
419
|
+
display: flex;
|
|
420
|
+
gap: 6px;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
#controls button {
|
|
424
|
+
background: transparent;
|
|
425
|
+
border: 1px solid var(--border-color);
|
|
426
|
+
color: var(--text-secondary);
|
|
427
|
+
padding: 8px;
|
|
428
|
+
border-radius: 6px;
|
|
429
|
+
cursor: pointer;
|
|
430
|
+
transition: all 0.2s ease;
|
|
431
|
+
font-size: 13px;
|
|
432
|
+
display: flex;
|
|
433
|
+
align-items: center;
|
|
434
|
+
justify-content: center;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
#controls button:hover {
|
|
438
|
+
background: var(--bg-secondary);
|
|
439
|
+
color: var(--text-primary);
|
|
440
|
+
border-color: var(--text-secondary);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
#controls button svg {
|
|
444
|
+
display: block;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
#content {
|
|
448
|
+
flex: 1;
|
|
449
|
+
overflow-y: auto;
|
|
450
|
+
background: var(--bg-primary);
|
|
451
|
+
padding: 48px;
|
|
452
|
+
display: flex;
|
|
453
|
+
gap: 32px;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
#toc-sidebar {
|
|
457
|
+
width: 250px;
|
|
458
|
+
background: var(--bg-secondary);
|
|
459
|
+
padding: 20px;
|
|
460
|
+
border-radius: 8px;
|
|
461
|
+
border: 1px solid var(--border-color);
|
|
462
|
+
overflow-y: auto;
|
|
463
|
+
max-height: calc(100vh - 140px);
|
|
464
|
+
position: sticky;
|
|
465
|
+
top: 20px;
|
|
466
|
+
display: none;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
#toc-sidebar.visible {
|
|
470
|
+
display: block;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
#toc-sidebar h3 {
|
|
474
|
+
margin-bottom: 15px;
|
|
475
|
+
color: var(--text-heading);
|
|
476
|
+
font-size: 1.1em;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
#toc-list {
|
|
480
|
+
list-style: none;
|
|
481
|
+
padding: 0;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
#toc-list li {
|
|
485
|
+
margin: 8px 0;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
#toc-list a {
|
|
489
|
+
color: var(--link-color);
|
|
490
|
+
text-decoration: none;
|
|
491
|
+
font-size: 0.9em;
|
|
492
|
+
transition: padding-left 0.2s ease;
|
|
493
|
+
display: block;
|
|
494
|
+
padding: 4px 0;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
#toc-list a:hover {
|
|
498
|
+
padding-left: 5px;
|
|
499
|
+
text-decoration: underline;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
#toc-list .toc-h2 { padding-left: 0; }
|
|
503
|
+
#toc-list .toc-h3 { padding-left: 15px; }
|
|
504
|
+
#toc-list .toc-h4 { padding-left: 30px; }
|
|
505
|
+
|
|
506
|
+
#markdown-container {
|
|
507
|
+
flex: 1;
|
|
508
|
+
min-width: 0;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
#markdown-content {
|
|
512
|
+
max-width: 900px;
|
|
513
|
+
margin: 0 auto;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/* Markdown Styling */
|
|
517
|
+
#markdown-content h1 {
|
|
518
|
+
font-size: clamp(1.8em, 4vw, 2.5em);
|
|
519
|
+
margin: 32px 0 20px;
|
|
520
|
+
padding-bottom: 12px;
|
|
521
|
+
border-bottom: 1px solid var(--border-color);
|
|
522
|
+
color: var(--text-heading);
|
|
523
|
+
letter-spacing: -0.02em;
|
|
524
|
+
font-weight: 700;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
#markdown-content h2 {
|
|
528
|
+
font-size: clamp(1.4em, 3vw, 2em);
|
|
529
|
+
margin: 32px 0 16px;
|
|
530
|
+
color: var(--text-heading);
|
|
531
|
+
letter-spacing: -0.02em;
|
|
532
|
+
font-weight: 600;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
#markdown-content h3 {
|
|
536
|
+
font-size: 1.5em;
|
|
537
|
+
margin: 24px 0 12px;
|
|
538
|
+
color: var(--text-heading);
|
|
539
|
+
letter-spacing: -0.01em;
|
|
540
|
+
font-weight: 600;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
#markdown-content h4, #markdown-content h5, #markdown-content h6 {
|
|
544
|
+
margin: 20px 0 8px;
|
|
545
|
+
color: var(--text-heading);
|
|
546
|
+
font-weight: 600;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
#markdown-content h3 + h1,
|
|
550
|
+
#markdown-content h3 + h2,
|
|
551
|
+
#markdown-content h4 + h1,
|
|
552
|
+
#markdown-content h4 + h2,
|
|
553
|
+
#markdown-content h5 + h1,
|
|
554
|
+
#markdown-content h6 + h1 {
|
|
555
|
+
margin-top: 0;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
#markdown-content h6 {
|
|
559
|
+
margin-bottom: 0;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
#markdown-content p {
|
|
563
|
+
margin: 16px 0;
|
|
564
|
+
line-height: 1.7;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
#markdown-content ul, #markdown-content ol {
|
|
568
|
+
margin: 16px 0;
|
|
569
|
+
padding-left: 32px;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
#markdown-content li {
|
|
573
|
+
margin: 8px 0;
|
|
574
|
+
line-height: 1.7;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
#markdown-content code {
|
|
578
|
+
background: var(--bg-code);
|
|
579
|
+
padding: 2px 6px;
|
|
580
|
+
border-radius: 3px;
|
|
581
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
582
|
+
font-size: 0.9em;
|
|
583
|
+
color: var(--text-primary);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
#markdown-content pre {
|
|
587
|
+
background: var(--bg-code);
|
|
588
|
+
border: 1px solid var(--border-color);
|
|
589
|
+
border-radius: 8px;
|
|
590
|
+
padding: 20px;
|
|
591
|
+
overflow-x: auto;
|
|
592
|
+
margin: 24px 0;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
#markdown-content pre code {
|
|
596
|
+
background: none;
|
|
597
|
+
padding: 0;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
#markdown-content blockquote {
|
|
601
|
+
border-left: 3px solid var(--border-color);
|
|
602
|
+
padding-left: 20px;
|
|
603
|
+
margin: 24px 0;
|
|
604
|
+
color: var(--text-secondary);
|
|
605
|
+
font-style: italic;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
#markdown-content table {
|
|
609
|
+
border-collapse: collapse;
|
|
610
|
+
width: 100%;
|
|
611
|
+
margin: 20px 0;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
#markdown-content th, #markdown-content td {
|
|
615
|
+
border: 1px solid var(--border-color);
|
|
616
|
+
padding: 12px;
|
|
617
|
+
text-align: left;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
#markdown-content th {
|
|
621
|
+
background: var(--bg-code);
|
|
622
|
+
font-weight: 600;
|
|
623
|
+
color: var(--text-heading);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
#markdown-content tr:nth-child(even) {
|
|
627
|
+
background: var(--table-stripe);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
#markdown-content a {
|
|
631
|
+
color: var(--link-color);
|
|
632
|
+
text-decoration: none;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
#markdown-content a:hover {
|
|
636
|
+
text-decoration: underline;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
#markdown-content hr {
|
|
640
|
+
border: none;
|
|
641
|
+
border-top: 2px solid var(--border-color);
|
|
642
|
+
margin: 30px 0;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/* Task lists (GFM) */
|
|
646
|
+
#markdown-content input[type="checkbox"] {
|
|
647
|
+
margin-right: 8px;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/* Mermaid diagrams */
|
|
651
|
+
.mermaid {
|
|
652
|
+
background: var(--bg-primary);
|
|
653
|
+
padding: 20px;
|
|
654
|
+
border-radius: 8px;
|
|
655
|
+
margin: 20px 0;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
#markdown-content img {
|
|
659
|
+
max-width: 100%;
|
|
660
|
+
height: auto;
|
|
661
|
+
display: block;
|
|
662
|
+
margin: 20px auto;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
.status-message {
|
|
667
|
+
position: fixed;
|
|
668
|
+
bottom: 20px;
|
|
669
|
+
right: 20px;
|
|
670
|
+
background: #333;
|
|
671
|
+
color: white;
|
|
672
|
+
padding: 10px 20px;
|
|
673
|
+
border-radius: 5px;
|
|
674
|
+
opacity: 0;
|
|
675
|
+
transition: opacity 0.3s ease;
|
|
676
|
+
z-index: 2000;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.status-message.show {
|
|
680
|
+
opacity: 0.9;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/* Responsive Design */
|
|
684
|
+
@media (max-width: 768px) {
|
|
685
|
+
#content {
|
|
686
|
+
padding: 20px;
|
|
687
|
+
flex-direction: column;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
#toc-sidebar {
|
|
691
|
+
width: 100%;
|
|
692
|
+
max-height: 200px;
|
|
693
|
+
position: relative;
|
|
694
|
+
top: 0;
|
|
695
|
+
margin-bottom: 20px;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
#header {
|
|
699
|
+
padding: 10px 15px;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
#file-info {
|
|
703
|
+
flex-direction: column;
|
|
704
|
+
align-items: flex-start;
|
|
705
|
+
gap: 5px;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
#controls {
|
|
709
|
+
flex-wrap: wrap;
|
|
710
|
+
width: 100%;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
button {
|
|
714
|
+
flex: 1;
|
|
715
|
+
min-width: 120px;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.drop-message h1 {
|
|
719
|
+
font-size: 2em;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.drop-message p {
|
|
723
|
+
font-size: 1em;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/* Print Styles */
|
|
728
|
+
@media print {
|
|
729
|
+
#header,
|
|
730
|
+
#toc-sidebar,
|
|
731
|
+
.status-message,
|
|
732
|
+
#drop-zone {
|
|
733
|
+
display: none !important;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
body {
|
|
737
|
+
background: white;
|
|
738
|
+
color: black;
|
|
739
|
+
overflow: visible;
|
|
740
|
+
height: auto;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
#viewer-container {
|
|
744
|
+
display: block !important;
|
|
745
|
+
height: auto;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
#content {
|
|
749
|
+
padding: 0;
|
|
750
|
+
background: white;
|
|
751
|
+
display: block;
|
|
752
|
+
overflow: visible;
|
|
753
|
+
height: auto;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
#markdown-container {
|
|
757
|
+
display: block;
|
|
758
|
+
width: 100%;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
#markdown-content {
|
|
762
|
+
max-width: 100%;
|
|
763
|
+
padding: 20px;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
#markdown-content a {
|
|
767
|
+
color: black;
|
|
768
|
+
text-decoration: underline;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
#markdown-content pre {
|
|
772
|
+
border: 1px solid #333;
|
|
773
|
+
page-break-inside: avoid;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
#markdown-content h1,
|
|
777
|
+
#markdown-content h2,
|
|
778
|
+
#markdown-content h3,
|
|
779
|
+
#markdown-content h4,
|
|
780
|
+
#markdown-content h5,
|
|
781
|
+
#markdown-content h6 {
|
|
782
|
+
page-break-after: avoid;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
#markdown-content table,
|
|
786
|
+
#markdown-content img {
|
|
787
|
+
page-break-inside: avoid;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
</style>
|
|
791
|
+
</head>
|
|
792
|
+
<body>
|
|
793
|
+
<div id="drop-zone">
|
|
794
|
+
<div class="drop-message">
|
|
795
|
+
<div class="brand">WIP Computer</div>
|
|
796
|
+
<h1>Live .md Viewer</h1>
|
|
797
|
+
<p class="tagline">For real-time preview, tell your AI<br>to open a file here together.</p>
|
|
798
|
+
<div class="drop-border">
|
|
799
|
+
<button onclick="document.getElementById('file-input-home').click()">Quick View</button>
|
|
800
|
+
<p class="drop-hint">Quick View doesn't update in real-time.</p>
|
|
801
|
+
</div>
|
|
802
|
+
<input type="file" id="file-input-home" accept=".md,.markdown" style="display: none;" onchange="handleHomeFileSelect(event)">
|
|
803
|
+
</div>
|
|
804
|
+
</div>
|
|
805
|
+
|
|
806
|
+
<div id="viewer-container" style="display: none;">
|
|
807
|
+
<div id="header">
|
|
808
|
+
<div id="file-info">
|
|
809
|
+
<span id="file-name">No file loaded</span>
|
|
810
|
+
<span id="live-status"></span>
|
|
811
|
+
<span id="last-updated"></span>
|
|
812
|
+
</div>
|
|
813
|
+
<div id="controls">
|
|
814
|
+
<button onclick="toggleTOC()" title="Table of Contents" aria-label="Toggle Table of Contents"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg></button>
|
|
815
|
+
<button id="theme-btn" onclick="toggleThemeDropdown()" title="Theme" aria-label="Theme"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v4"/><path d="m6.34 6.34 2.83 2.83"/><path d="M2 12h4"/><path d="m6.34 17.66 2.83-2.83"/><path d="M12 18v4"/><path d="m17.66 17.66-2.83-2.83"/><path d="M18 12h4"/><path d="m17.66 6.34-2.83 2.83"/></svg></button>
|
|
816
|
+
<div id="theme-dropdown">
|
|
817
|
+
<button class="theme-option active" onclick="setStyle('wip')"><span class="check">✓</span> WIP Theme</button>
|
|
818
|
+
<button class="theme-option" onclick="setStyle('github')"><span class="check"></span> GitHub Theme</button>
|
|
819
|
+
<div class="divider"></div>
|
|
820
|
+
<button class="theme-option" onclick="showThemePrompt()"><span class="check">+</span> Add Theme...</button>
|
|
821
|
+
</div>
|
|
822
|
+
<button onclick="toggleDarkMode()" title="Toggle Theme" aria-label="Toggle Dark Mode"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg></button>
|
|
823
|
+
<button onclick="showFolderWarning()" title="Quick View" aria-label="Quick View"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z"/></svg></button>
|
|
824
|
+
<div id="folder-warning">Opening a file this way won't connect your AI. For live updates, tell your AI to open the file with '.md viewer'.<br><br><a href="#" onclick="dismissWarningAndOpen(event)" style="color: var(--text-heading); font-weight: 600;">Open anyway</a></div>
|
|
825
|
+
<button onclick="clearViewer()" title="Home" aria-label="Clear"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
|
826
|
+
<input type="file" id="file-input" accept=".md,.markdown" style="display: none;" onchange="handleFileSelect(event)">
|
|
827
|
+
</div>
|
|
828
|
+
</div>
|
|
829
|
+
<div id="content">
|
|
830
|
+
<div id="toc-sidebar">
|
|
831
|
+
<h3>Table of Contents</h3>
|
|
832
|
+
<ul id="toc-list"></ul>
|
|
833
|
+
</div>
|
|
834
|
+
<div id="markdown-container">
|
|
835
|
+
<div id="markdown-content"></div>
|
|
836
|
+
</div>
|
|
837
|
+
</div>
|
|
838
|
+
</div>
|
|
839
|
+
|
|
840
|
+
<div id="status-message" class="status-message"></div>
|
|
841
|
+
<div id="theme-modal-overlay" onclick="closeThemePrompt()"></div>
|
|
842
|
+
<div id="theme-prompt-modal">
|
|
843
|
+
<h3>Add a Custom Theme</h3>
|
|
844
|
+
<div class="prompt-text" onclick="copyThemePrompt()" title="Click to copy">Tell your AI:<br><br>"Add a new theme called <b>[name]</b> to the .md viewer."<br><br><b>Available tokens:</b><br><code style="font-size:0.85em">--bg-primary</code> page background<br><code style="font-size:0.85em">--bg-secondary</code> sidebar, header<br><code style="font-size:0.85em">--bg-code</code> code blocks<br><code style="font-size:0.85em">--text-primary</code> body text<br><code style="font-size:0.85em">--text-secondary</code> muted text<br><code style="font-size:0.85em">--text-heading</code> headings<br><code style="font-size:0.85em">--border-color</code> borders<br><code style="font-size:0.85em">--link-color</code> links<br><code style="font-size:0.85em">--table-stripe</code> alternating rows<br><br>Plus heading sizes, letter-spacing, and font overrides via <code style="font-size:0.85em">[data-style="name"]</code> selectors.</div>
|
|
845
|
+
<div class="modal-actions">
|
|
846
|
+
<button onclick="closeThemePrompt()">Close</button>
|
|
847
|
+
<button class="primary" onclick="copyThemePrompt()">Copy Prompt</button>
|
|
848
|
+
</div>
|
|
849
|
+
</div>
|
|
850
|
+
|
|
851
|
+
<script>
|
|
852
|
+
let currentFile = null;
|
|
853
|
+
let lastModified = null;
|
|
854
|
+
let refreshInterval = null;
|
|
855
|
+
let fileHandle = null;
|
|
856
|
+
|
|
857
|
+
// Configure marked.js with GFM and sanitization
|
|
858
|
+
marked.setOptions({
|
|
859
|
+
gfm: true,
|
|
860
|
+
breaks: true,
|
|
861
|
+
sanitize: false, // We'll use DOMPurify-style approach
|
|
862
|
+
highlight: function(code, lang) {
|
|
863
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
864
|
+
try {
|
|
865
|
+
return hljs.highlight(code, { language: lang }).value;
|
|
866
|
+
} catch (err) {}
|
|
867
|
+
}
|
|
868
|
+
return hljs.highlightAuto(code).value;
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
// Initialize Mermaid
|
|
873
|
+
mermaid.initialize({
|
|
874
|
+
startOnLoad: false,
|
|
875
|
+
theme: 'default',
|
|
876
|
+
securityLevel: 'loose'
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
// Initialize
|
|
880
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
881
|
+
setupDragAndDrop();
|
|
882
|
+
loadThemePreference();
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
function handleHomeFileSelect(event) {
|
|
886
|
+
const file = event.target.files[0];
|
|
887
|
+
if (file && (file.name.endsWith('.md') || file.name.endsWith('.markdown'))) {
|
|
888
|
+
const reader = new FileReader();
|
|
889
|
+
reader.onload = (ev) => {
|
|
890
|
+
sessionStorage.setItem('mdview-content', ev.target.result);
|
|
891
|
+
sessionStorage.setItem('mdview-name', file.name);
|
|
892
|
+
window.location.href = '/view?name=' + encodeURIComponent(file.name);
|
|
893
|
+
};
|
|
894
|
+
reader.readAsText(file);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function setupDragAndDrop() {
|
|
899
|
+
const dropZone = document.getElementById('drop-zone');
|
|
900
|
+
const body = document.body;
|
|
901
|
+
|
|
902
|
+
// Prevent default drag behaviors
|
|
903
|
+
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
|
904
|
+
dropZone.addEventListener(eventName, preventDefaults, false);
|
|
905
|
+
body.addEventListener(eventName, preventDefaults, false);
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
// Highlight drop zone when item is dragged over it
|
|
909
|
+
['dragenter', 'dragover'].forEach(eventName => {
|
|
910
|
+
dropZone.addEventListener(eventName, () => {
|
|
911
|
+
dropZone.classList.add('dragover');
|
|
912
|
+
}, false);
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
['dragleave', 'drop'].forEach(eventName => {
|
|
916
|
+
dropZone.addEventListener(eventName, () => {
|
|
917
|
+
dropZone.classList.remove('dragover');
|
|
918
|
+
}, false);
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
// Handle dropped files
|
|
922
|
+
dropZone.addEventListener('drop', handleDrop, false);
|
|
923
|
+
|
|
924
|
+
// Also allow dragging to the viewer when a file is already loaded
|
|
925
|
+
body.addEventListener('drop', (e) => {
|
|
926
|
+
if (currentFile) {
|
|
927
|
+
preventDefaults(e);
|
|
928
|
+
handleDrop(e);
|
|
929
|
+
}
|
|
930
|
+
}, false);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function preventDefaults(e) {
|
|
934
|
+
e.preventDefault();
|
|
935
|
+
e.stopPropagation();
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
function handleDrop(e) {
|
|
939
|
+
const dt = e.dataTransfer;
|
|
940
|
+
const files = dt.files;
|
|
941
|
+
|
|
942
|
+
if (files.length > 0) {
|
|
943
|
+
const file = files[0];
|
|
944
|
+
if (file.name.endsWith('.md') || file.name.endsWith('.markdown')) {
|
|
945
|
+
// If on homepage, read content and navigate to viewer
|
|
946
|
+
if (window.location.pathname === '/' || window.location.pathname === '/index.html') {
|
|
947
|
+
const reader = new FileReader();
|
|
948
|
+
reader.onload = (ev) => {
|
|
949
|
+
sessionStorage.setItem('mdview-content', ev.target.result);
|
|
950
|
+
sessionStorage.setItem('mdview-name', file.name);
|
|
951
|
+
window.location.href = '/view?name=' + encodeURIComponent(file.name);
|
|
952
|
+
};
|
|
953
|
+
reader.readAsText(file);
|
|
954
|
+
} else {
|
|
955
|
+
loadFile(file);
|
|
956
|
+
}
|
|
957
|
+
} else {
|
|
958
|
+
showStatus('Please drop a Markdown file (.md or .markdown)', 3000);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
async function loadFile(file, isAutoRefresh = false) {
|
|
964
|
+
currentFile = file;
|
|
965
|
+
lastModified = file.lastModified;
|
|
966
|
+
|
|
967
|
+
// Read and display the file
|
|
968
|
+
const reader = new FileReader();
|
|
969
|
+
reader.onload = async (e) => {
|
|
970
|
+
const content = e.target.result;
|
|
971
|
+
await displayMarkdown(content);
|
|
972
|
+
|
|
973
|
+
// Update UI (only on initial load)
|
|
974
|
+
if (!isAutoRefresh) {
|
|
975
|
+
document.getElementById('drop-zone').classList.add('hidden');
|
|
976
|
+
document.getElementById('viewer-container').style.display = 'flex';
|
|
977
|
+
showStatus('File loaded successfully', 2000);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
document.getElementById('file-name').textContent = file.name;
|
|
981
|
+
updateLastModified();
|
|
982
|
+
|
|
983
|
+
// Start auto-refresh
|
|
984
|
+
await startAutoRefresh();
|
|
985
|
+
};
|
|
986
|
+
|
|
987
|
+
reader.onerror = () => {
|
|
988
|
+
showStatus('Error reading file', 3000);
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
reader.readAsText(file);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
async function displayMarkdown(content) {
|
|
995
|
+
// Parse markdown to HTML
|
|
996
|
+
const html = marked.parse(content);
|
|
997
|
+
|
|
998
|
+
// Sanitize HTML (basic XSS protection)
|
|
999
|
+
const tempDiv = document.createElement('div');
|
|
1000
|
+
tempDiv.innerHTML = html;
|
|
1001
|
+
|
|
1002
|
+
// Remove dangerous elements and attributes
|
|
1003
|
+
const scripts = tempDiv.querySelectorAll('script');
|
|
1004
|
+
scripts.forEach(script => script.remove());
|
|
1005
|
+
|
|
1006
|
+
const iframes = tempDiv.querySelectorAll('iframe');
|
|
1007
|
+
iframes.forEach(iframe => {
|
|
1008
|
+
// Only allow specific safe sources
|
|
1009
|
+
const src = iframe.getAttribute('src');
|
|
1010
|
+
if (!src || (!src.startsWith('https://') && !src.startsWith('http://'))) {
|
|
1011
|
+
iframe.remove();
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
// Set the sanitized content
|
|
1016
|
+
document.getElementById('markdown-content').innerHTML = tempDiv.innerHTML;
|
|
1017
|
+
|
|
1018
|
+
// Highlight code blocks
|
|
1019
|
+
document.querySelectorAll('pre code').forEach((block) => {
|
|
1020
|
+
hljs.highlightElement(block);
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
// Render Mermaid diagrams
|
|
1024
|
+
const mermaidBlocks = document.querySelectorAll('code.language-mermaid');
|
|
1025
|
+
for (let i = 0; i < mermaidBlocks.length; i++) {
|
|
1026
|
+
const block = mermaidBlocks[i];
|
|
1027
|
+
const code = block.textContent;
|
|
1028
|
+
const pre = block.parentElement;
|
|
1029
|
+
|
|
1030
|
+
try {
|
|
1031
|
+
const { svg } = await mermaid.render(`mermaid-${i}`, code);
|
|
1032
|
+
const div = document.createElement('div');
|
|
1033
|
+
div.className = 'mermaid';
|
|
1034
|
+
div.innerHTML = svg;
|
|
1035
|
+
pre.replaceWith(div);
|
|
1036
|
+
} catch (err) {
|
|
1037
|
+
console.error('Mermaid rendering error:', err);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// Render math equations with KaTeX
|
|
1042
|
+
renderMathInElement(document.getElementById('markdown-content'), {
|
|
1043
|
+
delimiters: [
|
|
1044
|
+
{left: '$$', right: '$$', display: true},
|
|
1045
|
+
{left: '$', right: '$', display: false},
|
|
1046
|
+
{left: '\\[', right: '\\]', display: true},
|
|
1047
|
+
{left: '\\(', right: '\\)', display: false}
|
|
1048
|
+
],
|
|
1049
|
+
throwOnError: false
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
// Generate Table of Contents and show it
|
|
1053
|
+
generateTOC();
|
|
1054
|
+
document.getElementById('toc-sidebar').classList.add('visible');
|
|
1055
|
+
|
|
1056
|
+
// Add IDs to headings for TOC links
|
|
1057
|
+
addHeadingIds();
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
function addHeadingIds() {
|
|
1061
|
+
const headings = document.querySelectorAll('#markdown-content h1, #markdown-content h2, #markdown-content h3');
|
|
1062
|
+
headings.forEach((heading, index) => {
|
|
1063
|
+
if (!heading.id) {
|
|
1064
|
+
const text = heading.textContent.trim();
|
|
1065
|
+
const id = text.toLowerCase()
|
|
1066
|
+
.replace(/[^\w\s-]/g, '')
|
|
1067
|
+
.replace(/\s+/g, '-')
|
|
1068
|
+
.replace(/--+/g, '-')
|
|
1069
|
+
.trim();
|
|
1070
|
+
heading.id = id || `heading-${index}`;
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function generateTOC() {
|
|
1076
|
+
const tocList = document.getElementById('toc-list');
|
|
1077
|
+
tocList.innerHTML = '';
|
|
1078
|
+
|
|
1079
|
+
const headings = document.querySelectorAll('#markdown-content h1, #markdown-content h2, #markdown-content h3');
|
|
1080
|
+
|
|
1081
|
+
if (headings.length === 0) {
|
|
1082
|
+
tocList.innerHTML = '<li style="color: var(--text-secondary); font-style: italic;">No headings found</li>';
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
headings.forEach((heading) => {
|
|
1087
|
+
const li = document.createElement('li');
|
|
1088
|
+
const a = document.createElement('a');
|
|
1089
|
+
a.textContent = heading.textContent;
|
|
1090
|
+
a.href = `#${heading.id}`;
|
|
1091
|
+
a.className = `toc-${heading.tagName.toLowerCase()}`;
|
|
1092
|
+
|
|
1093
|
+
a.addEventListener('click', (e) => {
|
|
1094
|
+
e.preventDefault();
|
|
1095
|
+
heading.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1096
|
+
|
|
1097
|
+
// Update URL without scrolling
|
|
1098
|
+
history.pushState(null, null, `#${heading.id}`);
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
li.appendChild(a);
|
|
1102
|
+
tocList.appendChild(li);
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
async function startAutoRefresh() {
|
|
1107
|
+
// Clear any existing interval
|
|
1108
|
+
if (refreshInterval) {
|
|
1109
|
+
clearInterval(refreshInterval);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// Try to use File System Access API if available
|
|
1113
|
+
if ('showOpenFilePicker' in window && fileHandle) {
|
|
1114
|
+
refreshInterval = setInterval(async () => {
|
|
1115
|
+
await checkForChanges();
|
|
1116
|
+
}, 2000);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
async function checkForChanges() {
|
|
1121
|
+
if (!fileHandle) return;
|
|
1122
|
+
|
|
1123
|
+
try {
|
|
1124
|
+
const file = await fileHandle.getFile();
|
|
1125
|
+
if (file.lastModified > lastModified) {
|
|
1126
|
+
// File has been modified, reload it
|
|
1127
|
+
await loadFile(file, true);
|
|
1128
|
+
showStatus('Content auto-refreshed', 2000);
|
|
1129
|
+
}
|
|
1130
|
+
} catch (err) {
|
|
1131
|
+
console.log('Auto-refresh not available:', err);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
async function refreshContent() {
|
|
1136
|
+
// First priority: If we have a fileHandle, use it to refresh silently
|
|
1137
|
+
if (fileHandle) {
|
|
1138
|
+
try {
|
|
1139
|
+
const file = await fileHandle.getFile();
|
|
1140
|
+
await loadFile(file);
|
|
1141
|
+
showStatus('Content refreshed', 2000);
|
|
1142
|
+
return;
|
|
1143
|
+
} catch (err) {
|
|
1144
|
+
console.log('Error refreshing from file handle:', err);
|
|
1145
|
+
// Handle lost, clear it and fall through to file picker
|
|
1146
|
+
fileHandle = null;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// Second priority: Use File System Access API with file picker
|
|
1151
|
+
if ('showOpenFilePicker' in window) {
|
|
1152
|
+
try {
|
|
1153
|
+
const [handle] = await window.showOpenFilePicker({
|
|
1154
|
+
types: [{
|
|
1155
|
+
description: 'Markdown Files',
|
|
1156
|
+
accept: { 'text/markdown': ['.md', '.markdown'] }
|
|
1157
|
+
}],
|
|
1158
|
+
multiple: false
|
|
1159
|
+
});
|
|
1160
|
+
fileHandle = handle;
|
|
1161
|
+
const file = await handle.getFile();
|
|
1162
|
+
await loadFile(file);
|
|
1163
|
+
showStatus('Content refreshed', 2000);
|
|
1164
|
+
return;
|
|
1165
|
+
} catch (err) {
|
|
1166
|
+
if (err.name !== 'AbortError') {
|
|
1167
|
+
console.log('File System Access API error:', err);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// Fallback to traditional file input
|
|
1173
|
+
if (currentFile) {
|
|
1174
|
+
document.getElementById('file-input').click();
|
|
1175
|
+
} else {
|
|
1176
|
+
showStatus('No file loaded', 2000);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
async function handleFileSelect(event) {
|
|
1181
|
+
const file = event.target.files[0];
|
|
1182
|
+
if (file && (file.name.endsWith('.md') || file.name.endsWith('.markdown'))) {
|
|
1183
|
+
await loadFile(file);
|
|
1184
|
+
showStatus('Content refreshed', 2000);
|
|
1185
|
+
// Reset the input so the same file can be selected again
|
|
1186
|
+
event.target.value = '';
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function clearViewer() {
|
|
1191
|
+
window.location.href = '/';
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
function loadNewFile() {
|
|
1195
|
+
document.getElementById('file-input').click();
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function showFolderWarning() {
|
|
1199
|
+
const warning = document.getElementById('folder-warning');
|
|
1200
|
+
warning.classList.toggle('show');
|
|
1201
|
+
if (warning.classList.contains('show')) {
|
|
1202
|
+
setTimeout(() => {
|
|
1203
|
+
document.addEventListener('click', dismissFolderWarning, { once: true });
|
|
1204
|
+
}, 0);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
function dismissFolderWarning(e) {
|
|
1209
|
+
const warning = document.getElementById('folder-warning');
|
|
1210
|
+
if (!warning.contains(e.target)) {
|
|
1211
|
+
warning.classList.remove('show');
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
function dismissWarningAndOpen(e) {
|
|
1216
|
+
e.preventDefault();
|
|
1217
|
+
document.getElementById('folder-warning').classList.remove('show');
|
|
1218
|
+
loadNewFile();
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function setLiveStatus(mode, agentName) {
|
|
1222
|
+
const el = document.getElementById('live-status');
|
|
1223
|
+
if (mode === 'live') {
|
|
1224
|
+
el.className = 'live';
|
|
1225
|
+
el.innerHTML = '<span class="dot"></span>' + (agentName || 'AI') + ' is editing';
|
|
1226
|
+
} else {
|
|
1227
|
+
el.className = 'static';
|
|
1228
|
+
el.innerHTML = '<span class="dot"></span>No AI is editing this file';
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
function updateLastModified() {
|
|
1233
|
+
const date = new Date(lastModified);
|
|
1234
|
+
const formatted = date.toLocaleString();
|
|
1235
|
+
document.getElementById('last-updated').textContent = `Last modified: ${formatted}`;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
function showStatus(message, duration = 2000) {
|
|
1239
|
+
const statusEl = document.getElementById('status-message');
|
|
1240
|
+
statusEl.textContent = message;
|
|
1241
|
+
statusEl.classList.add('show');
|
|
1242
|
+
|
|
1243
|
+
setTimeout(() => {
|
|
1244
|
+
statusEl.classList.remove('show');
|
|
1245
|
+
}, duration);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// Dark Mode Functions
|
|
1249
|
+
function toggleThemeDropdown() {
|
|
1250
|
+
const dropdown = document.getElementById('theme-dropdown');
|
|
1251
|
+
dropdown.classList.toggle('show');
|
|
1252
|
+
if (dropdown.classList.contains('show')) {
|
|
1253
|
+
updateThemeChecks();
|
|
1254
|
+
setTimeout(() => {
|
|
1255
|
+
document.addEventListener('click', closeThemeDropdown, { once: true });
|
|
1256
|
+
}, 0);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
function closeThemeDropdown(e) {
|
|
1261
|
+
const dropdown = document.getElementById('theme-dropdown');
|
|
1262
|
+
if (e && dropdown.contains(e.target)) return;
|
|
1263
|
+
dropdown.classList.remove('show');
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function updateThemeChecks() {
|
|
1267
|
+
const current = document.documentElement.getAttribute('data-style') || 'wip';
|
|
1268
|
+
const options = document.querySelectorAll('#theme-dropdown .theme-option');
|
|
1269
|
+
options.forEach(opt => {
|
|
1270
|
+
const style = opt.getAttribute('onclick')?.match(/setStyle\('(\w+)'\)/)?.[1];
|
|
1271
|
+
if (style) {
|
|
1272
|
+
opt.classList.toggle('active', style === current);
|
|
1273
|
+
opt.querySelector('.check').textContent = style === current ? '✓' : '';
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
function setStyle(style) {
|
|
1279
|
+
document.documentElement.setAttribute('data-style', style);
|
|
1280
|
+
localStorage.setItem('style', style);
|
|
1281
|
+
updateThemeChecks();
|
|
1282
|
+
document.getElementById('theme-dropdown').classList.remove('show');
|
|
1283
|
+
showStatus(style === 'github' ? 'GitHub Theme' : 'WIP Theme', 1500);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
function showThemePrompt() {
|
|
1287
|
+
document.getElementById('theme-dropdown').classList.remove('show');
|
|
1288
|
+
document.getElementById('theme-modal-overlay').classList.add('show');
|
|
1289
|
+
document.getElementById('theme-prompt-modal').classList.add('show');
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function closeThemePrompt() {
|
|
1293
|
+
document.getElementById('theme-modal-overlay').classList.remove('show');
|
|
1294
|
+
document.getElementById('theme-prompt-modal').classList.remove('show');
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function copyThemePrompt() {
|
|
1298
|
+
const text = 'Add a new theme called "[name]" to the .md viewer. Add a [data-style="name"] CSS block in markdown-viewer.html with these tokens: --bg-primary, --bg-secondary, --bg-code, --text-primary, --text-secondary, --text-heading, --border-color, --link-color, --table-stripe. You can also override heading sizes, letter-spacing, and fonts. Add it to the theme dropdown too.';
|
|
1299
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
1300
|
+
showStatus('Prompt copied', 1500);
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
function toggleDarkMode() {
|
|
1305
|
+
const html = document.documentElement;
|
|
1306
|
+
const currentTheme = html.getAttribute('data-theme');
|
|
1307
|
+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
1308
|
+
|
|
1309
|
+
html.setAttribute('data-theme', newTheme);
|
|
1310
|
+
localStorage.setItem('theme', newTheme);
|
|
1311
|
+
|
|
1312
|
+
// Update highlight.js theme
|
|
1313
|
+
const highlightTheme = document.getElementById('highlight-theme');
|
|
1314
|
+
if (newTheme === 'dark') {
|
|
1315
|
+
highlightTheme.href = 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css';
|
|
1316
|
+
mermaid.initialize({ theme: 'dark' });
|
|
1317
|
+
} else {
|
|
1318
|
+
highlightTheme.href = 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css';
|
|
1319
|
+
mermaid.initialize({ theme: 'default' });
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// Re-render current content if loaded
|
|
1323
|
+
if (currentFile) {
|
|
1324
|
+
const content = document.getElementById('markdown-content').textContent;
|
|
1325
|
+
if (content) {
|
|
1326
|
+
showStatus(`${newTheme === 'dark' ? 'Dark' : 'Light'} mode enabled`, 1500);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function loadThemePreference() {
|
|
1332
|
+
const savedTheme = localStorage.getItem('theme');
|
|
1333
|
+
if (savedTheme) {
|
|
1334
|
+
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
1335
|
+
|
|
1336
|
+
// Update highlight.js theme
|
|
1337
|
+
const highlightTheme = document.getElementById('highlight-theme');
|
|
1338
|
+
if (savedTheme === 'dark') {
|
|
1339
|
+
highlightTheme.href = 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css';
|
|
1340
|
+
mermaid.initialize({ theme: 'dark' });
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
const savedStyle = localStorage.getItem('style');
|
|
1344
|
+
if (savedStyle) {
|
|
1345
|
+
document.documentElement.setAttribute('data-style', savedStyle);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// TOC Functions
|
|
1350
|
+
function toggleTOC() {
|
|
1351
|
+
const tocSidebar = document.getElementById('toc-sidebar');
|
|
1352
|
+
tocSidebar.classList.toggle('visible');
|
|
1353
|
+
|
|
1354
|
+
const isVisible = tocSidebar.classList.contains('visible');
|
|
1355
|
+
showStatus(`TOC ${isVisible ? 'shown' : 'hidden'}`, 1000);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// Export Function
|
|
1359
|
+
function exportHTML() {
|
|
1360
|
+
if (!currentFile) {
|
|
1361
|
+
showStatus('No file loaded to export', 2000);
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
const markdownContent = document.getElementById('markdown-content').innerHTML;
|
|
1366
|
+
const fileName = currentFile.name.replace(/\.(md|markdown)$/, '.html');
|
|
1367
|
+
|
|
1368
|
+
const htmlTemplate = `<!DOCTYPE html>
|
|
1369
|
+
<html lang="en">
|
|
1370
|
+
<head>
|
|
1371
|
+
<meta charset="UTF-8">
|
|
1372
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1373
|
+
<title>${currentFile.name}</title>
|
|
1374
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css">
|
|
1375
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
|
|
1376
|
+
<style>
|
|
1377
|
+
body {
|
|
1378
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
|
|
1379
|
+
line-height: 1.6;
|
|
1380
|
+
max-width: 900px;
|
|
1381
|
+
margin: 40px auto;
|
|
1382
|
+
padding: 20px;
|
|
1383
|
+
color: #333;
|
|
1384
|
+
}
|
|
1385
|
+
pre { background: #f8f8f8; padding: 15px; border-radius: 5px; overflow-x: auto; }
|
|
1386
|
+
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }
|
|
1387
|
+
table { border-collapse: collapse; width: 100%; margin: 20px 0; }
|
|
1388
|
+
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
|
|
1389
|
+
th { background: #f8f8f8; font-weight: 600; }
|
|
1390
|
+
img { max-width: 100%; height: auto; }
|
|
1391
|
+
a { color: #667eea; text-decoration: none; }
|
|
1392
|
+
a:hover { text-decoration: underline; }
|
|
1393
|
+
</style>
|
|
1394
|
+
</head>
|
|
1395
|
+
<body>
|
|
1396
|
+
${markdownContent}
|
|
1397
|
+
</body>
|
|
1398
|
+
</html>`;
|
|
1399
|
+
|
|
1400
|
+
const blob = new Blob([htmlTemplate], { type: 'text/html' });
|
|
1401
|
+
const url = URL.createObjectURL(blob);
|
|
1402
|
+
const a = document.createElement('a');
|
|
1403
|
+
a.href = url;
|
|
1404
|
+
a.download = fileName;
|
|
1405
|
+
a.click();
|
|
1406
|
+
URL.revokeObjectURL(url);
|
|
1407
|
+
|
|
1408
|
+
showStatus('HTML exported successfully', 2000);
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
// Print Function
|
|
1412
|
+
function printDocument() {
|
|
1413
|
+
if (!currentFile) {
|
|
1414
|
+
showStatus('No file loaded to print', 2000);
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
window.print();
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
// Allow file re-dropping to refresh
|
|
1421
|
+
document.addEventListener('dragover', (e) => {
|
|
1422
|
+
if (currentFile) {
|
|
1423
|
+
e.preventDefault();
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
document.addEventListener('drop', async (e) => {
|
|
1428
|
+
if (currentFile && e.dataTransfer.files.length > 0) {
|
|
1429
|
+
e.preventDefault();
|
|
1430
|
+
const file = e.dataTransfer.files[0];
|
|
1431
|
+
if (file.name === currentFile.name) {
|
|
1432
|
+
// Same file dropped again - refresh it
|
|
1433
|
+
await loadFile(file);
|
|
1434
|
+
showStatus('Content refreshed', 2000);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1438
|
+
</script>
|
|
1439
|
+
</body>
|
|
1440
|
+
</html>
|