pinokiod 7.2.8 → 7.2.9

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.
@@ -0,0 +1,1546 @@
1
+ (function () {
2
+ if (window.__PinokioDraftsLoaded) {
3
+ return;
4
+ }
5
+ window.__PinokioDraftsLoaded = true;
6
+
7
+ const context = window.PinokioDraftContext || {};
8
+ const activeCwd = typeof context.cwd === "string" ? context.cwd.trim() : "";
9
+ if (!activeCwd) {
10
+ return;
11
+ }
12
+ const PUSH_ENDPOINT = "/push";
13
+ const SOUND_PREF_STORAGE_KEY = "pinokio:idle-sound";
14
+ const SOUND_SILENT_CHOICE = "__silent__";
15
+ const DRAFT_NOTIFIED_PREFIX = "pinokio:draft-notified:";
16
+ const state = {
17
+ expanded: new Set(),
18
+ initialRefreshComplete: false,
19
+ items: [],
20
+ lastSignature: "",
21
+ notifiedIds: new Set(),
22
+ drawerItemId: "",
23
+ drawerTab: "preview"
24
+ };
25
+ let pendingRegistryImport = null;
26
+
27
+ function resolveNotificationSound() {
28
+ try {
29
+ const raw = localStorage.getItem(SOUND_PREF_STORAGE_KEY);
30
+ const parsed = raw ? JSON.parse(raw) : null;
31
+ const choice = parsed && typeof parsed.choice === "string" ? parsed.choice.trim() : "";
32
+ if (choice === SOUND_SILENT_CHOICE) {
33
+ return false;
34
+ }
35
+ const withLeading = choice.startsWith("/") ? choice : `/${choice}`;
36
+ const decoded = decodeURIComponent(withLeading);
37
+ if (decoded.startsWith("/sound/") && !decoded.includes("..")) {
38
+ return withLeading;
39
+ }
40
+ } catch (_) {}
41
+ return true;
42
+ }
43
+
44
+ function claimDraftNotification(id) {
45
+ const normalized = typeof id === "string" ? id.trim() : "";
46
+ if (!normalized) {
47
+ return false;
48
+ }
49
+ if (state.notifiedIds.has(normalized)) {
50
+ return false;
51
+ }
52
+ state.notifiedIds.add(normalized);
53
+ try {
54
+ const key = `${DRAFT_NOTIFIED_PREFIX}${normalized}`;
55
+ if (localStorage.getItem(key)) {
56
+ return false;
57
+ }
58
+ const token = `${Date.now()}:${Math.random().toString(36).slice(2)}`;
59
+ localStorage.setItem(key, token);
60
+ return localStorage.getItem(key) === token;
61
+ } catch (_) {
62
+ return true;
63
+ }
64
+ }
65
+
66
+ function notifyDraftReady(item) {
67
+ const notificationKey = item && item.id
68
+ ? `${item.id}:${item.revision || item.updatedAt || ""}`
69
+ : "";
70
+ if (!item || !claimDraftNotification(notificationKey)) {
71
+ return;
72
+ }
73
+ const title = typeof item.title === "string" && item.title.trim() ? item.title.trim() : "Draft";
74
+ const workspaceName = typeof item.workspaceName === "string" && item.workspaceName.trim() ? item.workspaceName.trim() : "";
75
+ const message = workspaceName ? `${workspaceName}: ${title}` : `Draft ready: ${title}`;
76
+ const sound = resolveNotificationSound();
77
+ const playedInline = typeof window.PinokioPlayNotificationSound === "function"
78
+ ? window.PinokioPlayNotificationSound(sound)
79
+ : false;
80
+ const payload = {
81
+ title: "Pinokio",
82
+ message,
83
+ timeout: 60,
84
+ sound: playedInline ? false : sound,
85
+ audience: "device",
86
+ device_id: (typeof window.PinokioGetDeviceId === "function") ? window.PinokioGetDeviceId() : undefined
87
+ };
88
+ try {
89
+ fetch(PUSH_ENDPOINT, {
90
+ method: "POST",
91
+ headers: {
92
+ "Content-Type": "application/json"
93
+ },
94
+ credentials: "include",
95
+ body: JSON.stringify(payload)
96
+ }).catch(() => {});
97
+ } catch (_) {
98
+ }
99
+ }
100
+
101
+ function ensureStyles() {
102
+ if (document.getElementById("pinokio-drafts-style")) {
103
+ return;
104
+ }
105
+ const style = document.createElement("style");
106
+ style.id = "pinokio-drafts-style";
107
+ style.textContent = `
108
+ .pinokio-drafts {
109
+ position: fixed;
110
+ right: 16px;
111
+ bottom: 16px;
112
+ z-index: 2147483000;
113
+ display: flex;
114
+ flex-direction: column;
115
+ gap: 10px;
116
+ width: min(390px, calc(100vw - 24px));
117
+ pointer-events: none;
118
+ color: #111827;
119
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
120
+ }
121
+ .pinokio-draft-card {
122
+ border: 1px solid rgba(15, 23, 42, 0.16);
123
+ border-radius: 8px;
124
+ background: rgba(255, 255, 255, 0.96);
125
+ box-shadow: 0 14px 38px rgba(15, 23, 42, 0.18);
126
+ cursor: pointer;
127
+ overflow: hidden;
128
+ pointer-events: auto;
129
+ transition: border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease;
130
+ }
131
+ .pinokio-draft-card:hover,
132
+ .pinokio-draft-card:focus {
133
+ border-color: rgba(15, 23, 42, 0.28);
134
+ box-shadow: 0 16px 42px rgba(15, 23, 42, 0.22);
135
+ outline: none;
136
+ transform: translateY(-1px);
137
+ }
138
+ body.dark .pinokio-draft-card {
139
+ border-color: rgba(255, 255, 255, 0.16);
140
+ background: rgba(23, 23, 25, 0.96);
141
+ box-shadow: 0 16px 42px rgba(0, 0, 0, 0.38);
142
+ }
143
+ body.dark .pinokio-draft-card:hover,
144
+ body.dark .pinokio-draft-card:focus {
145
+ border-color: rgba(251, 191, 36, 0.38);
146
+ box-shadow: 0 18px 46px rgba(0, 0, 0, 0.46);
147
+ }
148
+ .pinokio-draft-head {
149
+ display: flex;
150
+ gap: 10px;
151
+ align-items: flex-start;
152
+ padding: 14px 14px 16px;
153
+ }
154
+ .pinokio-draft-icon {
155
+ display: grid;
156
+ place-items: center;
157
+ flex: 0 0 auto;
158
+ width: 30px;
159
+ height: 30px;
160
+ border-radius: 7px;
161
+ background: #fef3c7;
162
+ color: #a16207;
163
+ font-size: 14px;
164
+ }
165
+ body.dark .pinokio-draft-icon {
166
+ background: rgba(245, 158, 11, 0.18);
167
+ color: #fbbf24;
168
+ }
169
+ .pinokio-draft-copy {
170
+ min-width: 0;
171
+ flex: 1 1 auto;
172
+ }
173
+ .pinokio-draft-kicker {
174
+ display: inline-flex;
175
+ align-items: center;
176
+ gap: 5px;
177
+ color: #a16207;
178
+ font-size: 11px;
179
+ font-weight: 700;
180
+ letter-spacing: 0;
181
+ text-transform: uppercase;
182
+ line-height: 1.2;
183
+ margin-bottom: 3px;
184
+ }
185
+ body.dark .pinokio-draft-kicker {
186
+ color: #fbbf24;
187
+ }
188
+ .pinokio-draft-kicker i {
189
+ font-size: 10px;
190
+ }
191
+ .pinokio-draft-title {
192
+ color: #111827;
193
+ font-size: 14px;
194
+ font-weight: 700;
195
+ line-height: 1.25;
196
+ overflow-wrap: anywhere;
197
+ }
198
+ body.dark .pinokio-draft-title {
199
+ color: #f8fafc;
200
+ }
201
+ .pinokio-draft-meta {
202
+ display: flex;
203
+ flex-wrap: wrap;
204
+ gap: 4px 8px;
205
+ margin-top: 7px;
206
+ color: rgba(55, 65, 81, 0.78);
207
+ font-size: 12px;
208
+ line-height: 1.25;
209
+ }
210
+ body.dark .pinokio-draft-meta {
211
+ color: rgba(212, 212, 216, 0.78);
212
+ }
213
+ .pinokio-draft-close {
214
+ flex: 0 0 auto;
215
+ width: 28px;
216
+ height: 28px;
217
+ border: 0;
218
+ border-radius: 6px;
219
+ background: transparent;
220
+ color: rgba(55, 65, 81, 0.74);
221
+ cursor: pointer;
222
+ font-size: 16px;
223
+ line-height: 1;
224
+ }
225
+ body.dark .pinokio-draft-close {
226
+ color: rgba(212, 212, 216, 0.78);
227
+ }
228
+ .pinokio-draft-close:hover,
229
+ .pinokio-draft-close:focus {
230
+ background: rgba(15, 23, 42, 0.06);
231
+ color: #111827;
232
+ }
233
+ body.dark .pinokio-draft-close:hover,
234
+ body.dark .pinokio-draft-close:focus {
235
+ background: rgba(255, 255, 255, 0.08);
236
+ color: #ffffff;
237
+ }
238
+ .pinokio-draft-preview {
239
+ border-top: 1px solid rgba(15, 23, 42, 0.1);
240
+ padding: 10px 12px 0;
241
+ color: rgba(31, 41, 55, 0.9);
242
+ font-size: 12px;
243
+ line-height: 1.45;
244
+ }
245
+ body.dark .pinokio-draft-preview {
246
+ border-top-color: rgba(255, 255, 255, 0.12);
247
+ color: rgba(244, 244, 245, 0.9);
248
+ }
249
+ .pinokio-draft-path {
250
+ margin-top: 8px;
251
+ color: rgba(75, 85, 99, 0.82);
252
+ font-family: "SFMono-Regular", Consolas, monospace;
253
+ font-size: 11px;
254
+ line-height: 1.35;
255
+ overflow-wrap: anywhere;
256
+ }
257
+ body.dark .pinokio-draft-path {
258
+ color: rgba(161, 161, 170, 0.86);
259
+ }
260
+ .pinokio-draft-actions {
261
+ display: flex;
262
+ align-items: center;
263
+ gap: 8px;
264
+ padding: 10px 12px 12px;
265
+ }
266
+ .pinokio-draft-button {
267
+ display: inline-flex;
268
+ align-items: center;
269
+ justify-content: center;
270
+ gap: 6px;
271
+ min-height: 30px;
272
+ border: 1px solid rgba(15, 23, 42, 0.14);
273
+ border-radius: 6px;
274
+ background: rgba(255, 255, 255, 0.88);
275
+ color: #1f2937;
276
+ cursor: pointer;
277
+ font-size: 12px;
278
+ font-weight: 700;
279
+ line-height: 1.1;
280
+ padding: 7px 10px;
281
+ white-space: nowrap;
282
+ }
283
+ .pinokio-draft-button:hover,
284
+ .pinokio-draft-button:focus {
285
+ border-color: rgba(15, 23, 42, 0.24);
286
+ background: rgba(15, 23, 42, 0.04);
287
+ }
288
+ body.dark .pinokio-draft-button {
289
+ border-color: rgba(148, 163, 184, 0.28);
290
+ background: rgba(30, 41, 59, 0.92);
291
+ color: #f8fafc;
292
+ }
293
+ body.dark .pinokio-draft-button:hover,
294
+ body.dark .pinokio-draft-button:focus {
295
+ border-color: rgba(251, 191, 36, 0.56);
296
+ background: rgba(120, 53, 15, 0.32);
297
+ }
298
+ .pinokio-draft-button.secondary {
299
+ color: #374151;
300
+ background: rgba(255, 255, 255, 0.72);
301
+ }
302
+ body.dark .pinokio-draft-button.secondary {
303
+ color: #dbeafe;
304
+ background: transparent;
305
+ }
306
+ .pinokio-draft-drawer .pinokio-draft-button.secondary {
307
+ border-color: rgba(15, 23, 42, 0.18);
308
+ color: #1f2937;
309
+ background: #ffffff;
310
+ }
311
+ body.dark .pinokio-draft-drawer .pinokio-draft-button.secondary {
312
+ border-color: rgba(148, 163, 184, 0.28);
313
+ color: #dbeafe;
314
+ background: transparent;
315
+ }
316
+ .pinokio-draft-button:disabled {
317
+ cursor: default;
318
+ opacity: 0.68;
319
+ }
320
+ .pinokio-draft-more {
321
+ align-self: flex-end;
322
+ border-radius: 999px;
323
+ background: rgba(255, 255, 255, 0.92);
324
+ border: 1px solid rgba(15, 23, 42, 0.14);
325
+ color: rgba(55, 65, 81, 0.82);
326
+ font-size: 12px;
327
+ line-height: 1.2;
328
+ padding: 6px 10px;
329
+ pointer-events: auto;
330
+ }
331
+ body.dark .pinokio-draft-more {
332
+ background: rgba(23, 23, 25, 0.96);
333
+ border-color: rgba(255, 255, 255, 0.16);
334
+ color: rgba(212, 212, 216, 0.82);
335
+ }
336
+ .pinokio-draft-drawer-backdrop {
337
+ position: fixed;
338
+ inset: 0;
339
+ z-index: 2147483001;
340
+ background: rgba(15, 23, 42, 0.28);
341
+ display: flex;
342
+ justify-content: flex-end;
343
+ color: #111827;
344
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
345
+ }
346
+ .pinokio-draft-drawer {
347
+ width: min(920px, calc(100vw - 34px));
348
+ height: 100vh;
349
+ background: #fbfbfc;
350
+ border-left: 1px solid rgba(15, 23, 42, 0.12);
351
+ box-shadow: -24px 0 72px rgba(15, 23, 42, 0.24);
352
+ display: flex;
353
+ flex-direction: column;
354
+ }
355
+ body.dark .pinokio-draft-drawer {
356
+ background: #171719;
357
+ border-left-color: rgba(255, 255, 255, 0.1);
358
+ color: rgba(250, 250, 250, 0.96);
359
+ }
360
+ body.dark .pinokio-draft-drawer-backdrop {
361
+ background: rgba(0, 0, 0, 0.42);
362
+ }
363
+ .pinokio-draft-drawer-header {
364
+ display: flex;
365
+ align-items: flex-start;
366
+ gap: 16px;
367
+ padding: 22px 24px 18px;
368
+ border-bottom: 1px solid rgba(15, 23, 42, 0.1);
369
+ }
370
+ body.dark .pinokio-draft-drawer-header {
371
+ border-bottom-color: rgba(255, 255, 255, 0.1);
372
+ }
373
+ .pinokio-draft-drawer-title-block {
374
+ min-width: 0;
375
+ flex: 1 1 auto;
376
+ }
377
+ .pinokio-draft-drawer-kicker {
378
+ display: inline-flex;
379
+ align-items: center;
380
+ gap: 5px;
381
+ color: #a16207;
382
+ font-size: 11px;
383
+ font-weight: 800;
384
+ letter-spacing: 0;
385
+ text-transform: uppercase;
386
+ }
387
+ .pinokio-draft-drawer-kicker i {
388
+ font-size: 10px;
389
+ }
390
+ body.dark .pinokio-draft-drawer-kicker {
391
+ color: #fbbf24;
392
+ }
393
+ .pinokio-draft-drawer-title {
394
+ margin-top: 5px;
395
+ color: inherit;
396
+ font-size: 22px;
397
+ font-weight: 800;
398
+ line-height: 1.16;
399
+ overflow-wrap: anywhere;
400
+ }
401
+ .pinokio-draft-drawer-meta {
402
+ display: flex;
403
+ flex-wrap: wrap;
404
+ gap: 6px 10px;
405
+ margin-top: 9px;
406
+ color: rgba(55, 65, 81, 0.76);
407
+ font-size: 12px;
408
+ line-height: 1.3;
409
+ }
410
+ body.dark .pinokio-draft-drawer-meta {
411
+ color: rgba(212, 212, 216, 0.78);
412
+ }
413
+ .pinokio-draft-drawer-close {
414
+ width: 34px;
415
+ height: 34px;
416
+ border: 1px solid rgba(15, 23, 42, 0.12);
417
+ border-radius: 8px;
418
+ background: transparent;
419
+ color: inherit;
420
+ cursor: pointer;
421
+ font-size: 18px;
422
+ }
423
+ body.dark .pinokio-draft-drawer-close {
424
+ border-color: rgba(255, 255, 255, 0.12);
425
+ }
426
+ .pinokio-draft-drawer-close:hover,
427
+ .pinokio-draft-drawer-close:focus {
428
+ background: rgba(15, 23, 42, 0.06);
429
+ }
430
+ body.dark .pinokio-draft-drawer-close:hover,
431
+ body.dark .pinokio-draft-drawer-close:focus {
432
+ background: rgba(255, 255, 255, 0.08);
433
+ }
434
+ .pinokio-draft-drawer-toolbar {
435
+ display: flex;
436
+ align-items: center;
437
+ justify-content: space-between;
438
+ gap: 12px;
439
+ padding: 12px 24px;
440
+ border-bottom: 1px solid rgba(15, 23, 42, 0.1);
441
+ }
442
+ body.dark .pinokio-draft-drawer-toolbar {
443
+ border-bottom-color: rgba(255, 255, 255, 0.1);
444
+ }
445
+ .pinokio-draft-tabs {
446
+ display: inline-flex;
447
+ gap: 4px;
448
+ border: 1px solid rgba(15, 23, 42, 0.1);
449
+ border-radius: 8px;
450
+ padding: 3px;
451
+ background: rgba(15, 23, 42, 0.04);
452
+ }
453
+ body.dark .pinokio-draft-tabs {
454
+ background: rgba(255, 255, 255, 0.05);
455
+ border-color: rgba(255, 255, 255, 0.1);
456
+ }
457
+ .pinokio-draft-tab {
458
+ border: 0;
459
+ border-radius: 6px;
460
+ background: transparent;
461
+ color: rgba(55, 65, 81, 0.86);
462
+ cursor: pointer;
463
+ font-size: 12px;
464
+ font-weight: 800;
465
+ padding: 7px 10px;
466
+ }
467
+ .pinokio-draft-tab.is-active {
468
+ background: #ffffff;
469
+ color: #111827;
470
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
471
+ }
472
+ body.dark .pinokio-draft-tab {
473
+ color: rgba(228, 228, 231, 0.78);
474
+ }
475
+ body.dark .pinokio-draft-tab.is-active {
476
+ background: rgba(255, 255, 255, 0.12);
477
+ color: #fafafa;
478
+ }
479
+ .pinokio-draft-drawer-actions {
480
+ display: flex;
481
+ flex-wrap: wrap;
482
+ justify-content: flex-end;
483
+ gap: 8px;
484
+ }
485
+ .pinokio-draft-drawer-body {
486
+ min-height: 0;
487
+ flex: 1 1 auto;
488
+ overflow: auto;
489
+ padding: 22px 24px 28px;
490
+ }
491
+ .pinokio-draft-markdown {
492
+ max-width: 760px;
493
+ color: #1f2937;
494
+ font-size: 15px;
495
+ line-height: 1.62;
496
+ }
497
+ body.dark .pinokio-draft-markdown {
498
+ color: rgba(244, 244, 245, 0.92);
499
+ }
500
+ .pinokio-draft-markdown h1,
501
+ .pinokio-draft-markdown h2,
502
+ .pinokio-draft-markdown h3 {
503
+ color: inherit;
504
+ line-height: 1.18;
505
+ letter-spacing: 0;
506
+ margin: 24px 0 10px;
507
+ }
508
+ .pinokio-draft-markdown h1 { font-size: 26px; }
509
+ .pinokio-draft-markdown h2 { font-size: 21px; }
510
+ .pinokio-draft-markdown h3 { font-size: 17px; }
511
+ .pinokio-draft-markdown p,
512
+ .pinokio-draft-markdown ul,
513
+ .pinokio-draft-markdown ol,
514
+ .pinokio-draft-markdown blockquote,
515
+ .pinokio-draft-markdown pre {
516
+ margin: 0 0 14px;
517
+ }
518
+ .pinokio-draft-markdown ul,
519
+ .pinokio-draft-markdown ol {
520
+ padding-left: 24px;
521
+ }
522
+ .pinokio-draft-markdown a {
523
+ color: #2563eb;
524
+ text-decoration: none;
525
+ }
526
+ .pinokio-draft-markdown a:hover {
527
+ text-decoration: underline;
528
+ }
529
+ body.dark .pinokio-draft-markdown a {
530
+ color: #93c5fd;
531
+ }
532
+ .pinokio-draft-markdown code {
533
+ border: 1px solid rgba(15, 23, 42, 0.1);
534
+ border-radius: 5px;
535
+ background: rgba(15, 23, 42, 0.05);
536
+ font-family: "SFMono-Regular", Consolas, monospace;
537
+ font-size: 0.9em;
538
+ padding: 1px 4px;
539
+ }
540
+ body.dark .pinokio-draft-markdown code {
541
+ border-color: rgba(255, 255, 255, 0.12);
542
+ background: rgba(255, 255, 255, 0.07);
543
+ }
544
+ .pinokio-draft-markdown pre {
545
+ overflow: auto;
546
+ border: 1px solid rgba(15, 23, 42, 0.1);
547
+ border-radius: 8px;
548
+ background: rgba(15, 23, 42, 0.05);
549
+ padding: 12px;
550
+ }
551
+ .pinokio-draft-markdown pre code {
552
+ border: 0;
553
+ background: transparent;
554
+ padding: 0;
555
+ }
556
+ .pinokio-draft-markdown blockquote {
557
+ border-left: 3px solid rgba(15, 23, 42, 0.2);
558
+ color: rgba(55, 65, 81, 0.84);
559
+ padding-left: 12px;
560
+ }
561
+ body.dark .pinokio-draft-markdown blockquote {
562
+ border-left-color: rgba(255, 255, 255, 0.24);
563
+ color: rgba(212, 212, 216, 0.84);
564
+ }
565
+ .pinokio-draft-markdown table {
566
+ border-collapse: collapse;
567
+ width: 100%;
568
+ margin: 0 0 16px;
569
+ font-size: 13px;
570
+ }
571
+ .pinokio-draft-markdown th,
572
+ .pinokio-draft-markdown td {
573
+ border: 1px solid rgba(15, 23, 42, 0.12);
574
+ padding: 8px 9px;
575
+ text-align: left;
576
+ vertical-align: top;
577
+ }
578
+ .pinokio-draft-markdown th {
579
+ background: rgba(15, 23, 42, 0.05);
580
+ font-weight: 800;
581
+ }
582
+ body.dark .pinokio-draft-markdown th,
583
+ body.dark .pinokio-draft-markdown td {
584
+ border-color: rgba(255, 255, 255, 0.12);
585
+ }
586
+ body.dark .pinokio-draft-markdown th {
587
+ background: rgba(255, 255, 255, 0.08);
588
+ }
589
+ .pinokio-draft-media-figure {
590
+ margin: 0 0 16px;
591
+ }
592
+ .pinokio-draft-media-figure img,
593
+ .pinokio-draft-media-figure video {
594
+ display: block;
595
+ max-width: 100%;
596
+ max-height: 520px;
597
+ border: 1px solid rgba(15, 23, 42, 0.1);
598
+ border-radius: 8px;
599
+ background: rgba(15, 23, 42, 0.04);
600
+ }
601
+ .pinokio-draft-media-figure audio {
602
+ width: min(520px, 100%);
603
+ }
604
+ body.dark .pinokio-draft-media-figure img,
605
+ body.dark .pinokio-draft-media-figure video {
606
+ border-color: rgba(255, 255, 255, 0.12);
607
+ background: rgba(255, 255, 255, 0.06);
608
+ }
609
+ .pinokio-draft-media-caption {
610
+ margin-top: 6px;
611
+ color: rgba(55, 65, 81, 0.72);
612
+ font-size: 12px;
613
+ }
614
+ body.dark .pinokio-draft-media-caption {
615
+ color: rgba(212, 212, 216, 0.72);
616
+ }
617
+ .pinokio-draft-raw {
618
+ box-sizing: border-box;
619
+ width: 100%;
620
+ min-height: calc(100vh - 210px);
621
+ margin: 0;
622
+ border: 1px solid rgba(15, 23, 42, 0.12);
623
+ border-radius: 8px;
624
+ background: #ffffff;
625
+ color: #111827;
626
+ overflow: auto;
627
+ padding: 14px;
628
+ white-space: pre-wrap;
629
+ word-break: break-word;
630
+ font-family: "SFMono-Regular", Consolas, monospace;
631
+ font-size: 13px;
632
+ line-height: 1.55;
633
+ }
634
+ body.dark .pinokio-draft-raw {
635
+ border-color: rgba(255, 255, 255, 0.12);
636
+ background: rgba(255, 255, 255, 0.04);
637
+ color: rgba(250, 250, 250, 0.92);
638
+ }
639
+ .pinokio-draft-media-list {
640
+ display: grid;
641
+ gap: 10px;
642
+ max-width: 760px;
643
+ }
644
+ .pinokio-draft-media-item {
645
+ display: flex;
646
+ align-items: center;
647
+ gap: 12px;
648
+ border: 1px solid rgba(15, 23, 42, 0.1);
649
+ border-radius: 8px;
650
+ background: rgba(255, 255, 255, 0.72);
651
+ padding: 11px 12px;
652
+ }
653
+ body.dark .pinokio-draft-media-item {
654
+ border-color: rgba(255, 255, 255, 0.1);
655
+ background: rgba(255, 255, 255, 0.05);
656
+ }
657
+ .pinokio-draft-media-item-icon {
658
+ display: grid;
659
+ place-items: center;
660
+ width: 32px;
661
+ height: 32px;
662
+ border-radius: 7px;
663
+ background: rgba(15, 23, 42, 0.06);
664
+ color: #0f766e;
665
+ }
666
+ body.dark .pinokio-draft-media-item-icon {
667
+ background: rgba(255, 255, 255, 0.08);
668
+ color: #5eead4;
669
+ }
670
+ .pinokio-draft-media-item-copy {
671
+ min-width: 0;
672
+ flex: 1 1 auto;
673
+ }
674
+ .pinokio-draft-media-item-title {
675
+ font-size: 13px;
676
+ font-weight: 800;
677
+ overflow-wrap: anywhere;
678
+ }
679
+ .pinokio-draft-media-item-meta {
680
+ margin-top: 3px;
681
+ color: rgba(55, 65, 81, 0.72);
682
+ font-size: 12px;
683
+ }
684
+ body.dark .pinokio-draft-media-item-meta {
685
+ color: rgba(212, 212, 216, 0.72);
686
+ }
687
+ @media (max-width: 520px) {
688
+ .pinokio-drafts {
689
+ right: 12px;
690
+ bottom: 12px;
691
+ }
692
+ .pinokio-draft-actions {
693
+ flex-wrap: wrap;
694
+ }
695
+ .pinokio-draft-drawer {
696
+ width: 100vw;
697
+ }
698
+ .pinokio-draft-drawer-header,
699
+ .pinokio-draft-drawer-toolbar,
700
+ .pinokio-draft-drawer-body {
701
+ padding-left: 16px;
702
+ padding-right: 16px;
703
+ }
704
+ }
705
+ `;
706
+ document.head.appendChild(style);
707
+ }
708
+
709
+ function getRoot() {
710
+ ensureStyles();
711
+ let root = document.getElementById("pinokio-drafts");
712
+ if (!root) {
713
+ root = document.createElement("div");
714
+ root.id = "pinokio-drafts";
715
+ root.className = "pinokio-drafts";
716
+ root.setAttribute("aria-live", "polite");
717
+ document.body.appendChild(root);
718
+ }
719
+ return root;
720
+ }
721
+
722
+ function removeRoot() {
723
+ const root = document.getElementById("pinokio-drafts");
724
+ if (root) {
725
+ root.remove();
726
+ }
727
+ }
728
+
729
+ function createElement(tag, className, text) {
730
+ const element = document.createElement(tag);
731
+ if (className) {
732
+ element.className = className;
733
+ }
734
+ if (text !== undefined && text !== null) {
735
+ element.textContent = text;
736
+ }
737
+ return element;
738
+ }
739
+
740
+ function createIcon(name) {
741
+ const icon = createElement("i", name);
742
+ icon.setAttribute("aria-hidden", "true");
743
+ return icon;
744
+ }
745
+
746
+ function createActionButton(className, label, iconName) {
747
+ const button = createElement("button", className);
748
+ if (iconName) {
749
+ button.appendChild(createIcon(iconName));
750
+ }
751
+ const text = createElement("span", "", label);
752
+ text.setAttribute("data-pinokio-draft-label", "true");
753
+ button.appendChild(text);
754
+ return button;
755
+ }
756
+
757
+ function setActionButtonLabel(button, label) {
758
+ if (!button) {
759
+ return;
760
+ }
761
+ const text = button.querySelector("[data-pinokio-draft-label]");
762
+ if (text) {
763
+ text.textContent = label;
764
+ } else {
765
+ button.textContent = label;
766
+ }
767
+ }
768
+
769
+ function createDraftStatus(className) {
770
+ const status = createElement("div", className);
771
+ status.appendChild(createIcon("fa-solid fa-circle-check"));
772
+ status.appendChild(document.createTextNode("Draft ready"));
773
+ return status;
774
+ }
775
+
776
+ function canPublishToRegistry(item) {
777
+ const publish = item && item.publish && typeof item.publish === "object" ? item.publish : null;
778
+ const target = publish && typeof publish.target === "string" ? publish.target.trim().toLowerCase() : "";
779
+ const type = publish && typeof publish.type === "string" ? publish.type.trim().toLowerCase() : "post";
780
+ return target === "registry" && type === "post";
781
+ }
782
+
783
+ function formatBytes(bytes) {
784
+ const value = Number(bytes);
785
+ if (!Number.isFinite(value) || value < 0) {
786
+ return "";
787
+ }
788
+ if (value < 1024) {
789
+ return `${value} B`;
790
+ }
791
+ if (value < 1024 * 1024) {
792
+ return `${(value / 1024).toFixed(1)} KB`;
793
+ }
794
+ return `${(value / 1024 / 1024).toFixed(1)} MB`;
795
+ }
796
+
797
+ function formatUpdatedAt(value) {
798
+ const date = new Date(value);
799
+ if (!Number.isFinite(date.getTime())) {
800
+ return "";
801
+ }
802
+ return date.toLocaleString([], {
803
+ month: "short",
804
+ day: "numeric",
805
+ hour: "numeric",
806
+ minute: "2-digit"
807
+ });
808
+ }
809
+
810
+ function getItemById(id) {
811
+ return (Array.isArray(state.items) ? state.items : []).find((item) => item && item.id === id) || null;
812
+ }
813
+
814
+ function getDraftMetaParts(item) {
815
+ const parts = [];
816
+ const updatedAt = formatUpdatedAt(item && item.updatedAt);
817
+ const postSize = formatBytes(item && item.postBytes);
818
+ const mediaCount = Number(item && item.mediaCount || 0);
819
+ const missingMedia = Number(item && item.missingMediaCount || 0);
820
+ if (item && item.workspaceName) {
821
+ parts.push(item.workspaceName);
822
+ }
823
+ if (updatedAt) {
824
+ parts.push(updatedAt);
825
+ }
826
+ if (postSize) {
827
+ parts.push(postSize);
828
+ }
829
+ if (mediaCount > 0) {
830
+ parts.push(`${mediaCount} media ${mediaCount === 1 ? "file" : "files"}`);
831
+ }
832
+ if (missingMedia > 0) {
833
+ parts.push(`${missingMedia} missing`);
834
+ }
835
+ return parts;
836
+ }
837
+
838
+ function normalizeRef(value) {
839
+ const raw = String(value || "").trim().replace(/^<|>$/g, "").replace(/\\/g, "/");
840
+ if (!raw) {
841
+ return "";
842
+ }
843
+ const withoutHash = raw.split("#")[0];
844
+ return withoutHash.split("?")[0];
845
+ }
846
+
847
+ function getMediaItems(item) {
848
+ return Array.isArray(item && item.media) ? item.media : [];
849
+ }
850
+
851
+ function findMediaByRef(item, ref) {
852
+ const normalized = normalizeRef(ref);
853
+ if (!normalized) {
854
+ return null;
855
+ }
856
+ return getMediaItems(item).find((media) => normalizeRef(media && media.ref) === normalized) || null;
857
+ }
858
+
859
+ function getMediaUrl(item, media) {
860
+ return `/drafts/${encodeURIComponent(item.id)}/media/${encodeURIComponent(String(media.index))}`;
861
+ }
862
+
863
+ function mediaKind(media) {
864
+ const ext = String((media && media.ext) || "").toLowerCase();
865
+ if ([".apng", ".avif", ".gif", ".jpeg", ".jpg", ".png", ".svg", ".webp"].includes(ext)) {
866
+ return "image";
867
+ }
868
+ if ([".mp4", ".webm", ".ogg"].includes(ext)) {
869
+ return "video";
870
+ }
871
+ if ([".m4a", ".mp3", ".wav"].includes(ext)) {
872
+ return "audio";
873
+ }
874
+ return "file";
875
+ }
876
+
877
+ function isSafeExternalHref(value) {
878
+ return /^(https?:|mailto:)/i.test(String(value || "").trim());
879
+ }
880
+
881
+ function appendInline(parent, text, item) {
882
+ const source = String(text || "");
883
+ const pattern = /(`[^`]+`|\*\*[^*]+\*\*|\[[^\]]+]\([^)]+\))/g;
884
+ let cursor = 0;
885
+ let match = null;
886
+ while ((match = pattern.exec(source))) {
887
+ if (match.index > cursor) {
888
+ parent.appendChild(document.createTextNode(source.slice(cursor, match.index)));
889
+ }
890
+ const token = match[0];
891
+ if (token.startsWith("`") && token.endsWith("`")) {
892
+ parent.appendChild(createElement("code", "", token.slice(1, -1)));
893
+ } else if (token.startsWith("**") && token.endsWith("**")) {
894
+ const strong = createElement("strong");
895
+ appendInline(strong, token.slice(2, -2), item);
896
+ parent.appendChild(strong);
897
+ } else {
898
+ const linkMatch = token.match(/^\[([^\]]+)]\(([^)]+)\)$/);
899
+ const label = linkMatch ? linkMatch[1] : token;
900
+ const href = linkMatch ? String(linkMatch[2] || "").trim() : "";
901
+ const media = findMediaByRef(item, href);
902
+ const safeHref = media && media.exists
903
+ ? getMediaUrl(item, media)
904
+ : (isSafeExternalHref(href) ? href : "");
905
+ if (safeHref) {
906
+ const link = createElement("a");
907
+ link.href = safeHref;
908
+ if (isSafeExternalHref(safeHref)) {
909
+ link.target = "_blank";
910
+ link.rel = "noreferrer";
911
+ }
912
+ link.textContent = label;
913
+ parent.appendChild(link);
914
+ } else {
915
+ parent.appendChild(document.createTextNode(label));
916
+ }
917
+ }
918
+ cursor = pattern.lastIndex;
919
+ }
920
+ if (cursor < source.length) {
921
+ parent.appendChild(document.createTextNode(source.slice(cursor)));
922
+ }
923
+ }
924
+
925
+ function createMissingMedia(ref) {
926
+ const figure = createElement("figure", "pinokio-draft-media-figure");
927
+ const missing = createElement("div", "pinokio-draft-media-item");
928
+ const icon = createElement("div", "pinokio-draft-media-item-icon");
929
+ icon.appendChild(createIcon("fa-solid fa-triangle-exclamation"));
930
+ const copy = createElement("div", "pinokio-draft-media-item-copy");
931
+ copy.appendChild(createElement("div", "pinokio-draft-media-item-title", ref || "Missing media"));
932
+ copy.appendChild(createElement("div", "pinokio-draft-media-item-meta", "Referenced file was not found in the draft folder."));
933
+ missing.appendChild(icon);
934
+ missing.appendChild(copy);
935
+ figure.appendChild(missing);
936
+ return figure;
937
+ }
938
+
939
+ function createMediaFigure(item, ref, altText) {
940
+ const media = findMediaByRef(item, ref);
941
+ if (!media || !media.exists) {
942
+ return createMissingMedia(ref);
943
+ }
944
+ const figure = createElement("figure", "pinokio-draft-media-figure");
945
+ const kind = mediaKind(media);
946
+ const url = getMediaUrl(item, media);
947
+ let element = null;
948
+ if (kind === "image") {
949
+ element = document.createElement("img");
950
+ element.alt = altText || media.ref || "";
951
+ element.loading = "lazy";
952
+ } else if (kind === "video") {
953
+ element = document.createElement("video");
954
+ element.controls = true;
955
+ element.preload = "metadata";
956
+ } else if (kind === "audio") {
957
+ element = document.createElement("audio");
958
+ element.controls = true;
959
+ element.preload = "metadata";
960
+ }
961
+ if (!element) {
962
+ const link = createElement("a", "", media.ref || "Open media");
963
+ link.href = url;
964
+ figure.appendChild(link);
965
+ } else {
966
+ element.src = url;
967
+ figure.appendChild(element);
968
+ }
969
+ const captionBits = [media.ref || ""];
970
+ const size = formatBytes(media.bytes);
971
+ if (size) {
972
+ captionBits.push(size);
973
+ }
974
+ figure.appendChild(createElement("figcaption", "pinokio-draft-media-caption", captionBits.filter(Boolean).join(" / ")));
975
+ return figure;
976
+ }
977
+
978
+ function splitTableRow(line) {
979
+ let value = String(line || "").trim();
980
+ if (value.startsWith("|")) {
981
+ value = value.slice(1);
982
+ }
983
+ if (value.endsWith("|")) {
984
+ value = value.slice(0, -1);
985
+ }
986
+ return value.split("|").map((cell) => cell.trim());
987
+ }
988
+
989
+ function isMarkdownTableSeparator(line) {
990
+ const cells = splitTableRow(line);
991
+ return cells.length > 1 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
992
+ }
993
+
994
+ function renderMarkdownTable(root, item, headerLine, rowLines) {
995
+ const table = createElement("table");
996
+ const thead = createElement("thead");
997
+ const headerRow = createElement("tr");
998
+ splitTableRow(headerLine).forEach((cell) => {
999
+ const th = createElement("th");
1000
+ appendInline(th, cell, item);
1001
+ headerRow.appendChild(th);
1002
+ });
1003
+ thead.appendChild(headerRow);
1004
+ table.appendChild(thead);
1005
+ const tbody = createElement("tbody");
1006
+ rowLines.forEach((line) => {
1007
+ const row = createElement("tr");
1008
+ splitTableRow(line).forEach((cell) => {
1009
+ const td = createElement("td");
1010
+ appendInline(td, cell, item);
1011
+ row.appendChild(td);
1012
+ });
1013
+ tbody.appendChild(row);
1014
+ });
1015
+ table.appendChild(tbody);
1016
+ root.appendChild(table);
1017
+ }
1018
+
1019
+ function renderMarkdownPreview(container, item) {
1020
+ const markdown = String((item && item.markdown) || "");
1021
+ const lines = markdown.split(/\r?\n/);
1022
+ const root = createElement("div", "pinokio-draft-markdown");
1023
+ let paragraph = [];
1024
+ let list = null;
1025
+ let code = null;
1026
+
1027
+ const flushParagraph = () => {
1028
+ if (!paragraph.length) {
1029
+ return;
1030
+ }
1031
+ const p = createElement("p");
1032
+ appendInline(p, paragraph.join(" "), item);
1033
+ root.appendChild(p);
1034
+ paragraph = [];
1035
+ };
1036
+ const flushList = () => {
1037
+ if (list) {
1038
+ root.appendChild(list);
1039
+ list = null;
1040
+ }
1041
+ };
1042
+ const flushCode = () => {
1043
+ if (!code) {
1044
+ return;
1045
+ }
1046
+ const pre = createElement("pre");
1047
+ const codeEl = createElement("code", "", code.join("\n"));
1048
+ pre.appendChild(codeEl);
1049
+ root.appendChild(pre);
1050
+ code = null;
1051
+ };
1052
+
1053
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
1054
+ const line = lines[lineIndex];
1055
+ const fenceMatch = line.match(/^```/);
1056
+ if (fenceMatch) {
1057
+ if (code) {
1058
+ flushCode();
1059
+ } else {
1060
+ flushParagraph();
1061
+ flushList();
1062
+ code = [];
1063
+ }
1064
+ continue;
1065
+ }
1066
+ if (code) {
1067
+ code.push(line);
1068
+ continue;
1069
+ }
1070
+ if (!line.trim()) {
1071
+ flushParagraph();
1072
+ flushList();
1073
+ continue;
1074
+ }
1075
+ if (line.trim().startsWith("|") && isMarkdownTableSeparator(lines[lineIndex + 1])) {
1076
+ flushParagraph();
1077
+ flushList();
1078
+ const rowLines = [];
1079
+ lineIndex += 1;
1080
+ while (lineIndex + 1 < lines.length && lines[lineIndex + 1].trim().startsWith("|")) {
1081
+ lineIndex += 1;
1082
+ rowLines.push(lines[lineIndex]);
1083
+ }
1084
+ renderMarkdownTable(root, item, line, rowLines);
1085
+ continue;
1086
+ }
1087
+ const imageMatch = line.trim().match(/^!\[([^\]]*)]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)$/);
1088
+ if (imageMatch) {
1089
+ flushParagraph();
1090
+ flushList();
1091
+ root.appendChild(createMediaFigure(item, imageMatch[2], imageMatch[1]));
1092
+ continue;
1093
+ }
1094
+ const heading = line.match(/^(#{1,3})\s+(.+?)\s*#*\s*$/);
1095
+ if (heading) {
1096
+ flushParagraph();
1097
+ flushList();
1098
+ const h = createElement(`h${heading[1].length}`);
1099
+ appendInline(h, heading[2], item);
1100
+ root.appendChild(h);
1101
+ continue;
1102
+ }
1103
+ const quote = line.match(/^>\s?(.*)$/);
1104
+ if (quote) {
1105
+ flushParagraph();
1106
+ flushList();
1107
+ const blockquote = createElement("blockquote");
1108
+ appendInline(blockquote, quote[1], item);
1109
+ root.appendChild(blockquote);
1110
+ continue;
1111
+ }
1112
+ const unordered = line.match(/^\s*[-*]\s+(.+)$/);
1113
+ const ordered = line.match(/^\s*\d+[.)]\s+(.+)$/);
1114
+ if (unordered || ordered) {
1115
+ flushParagraph();
1116
+ const tag = ordered ? "ol" : "ul";
1117
+ if (!list || list.tagName.toLowerCase() !== tag) {
1118
+ flushList();
1119
+ list = createElement(tag);
1120
+ }
1121
+ const li = createElement("li");
1122
+ appendInline(li, (unordered || ordered)[1], item);
1123
+ list.appendChild(li);
1124
+ continue;
1125
+ }
1126
+ paragraph.push(line.trim());
1127
+ }
1128
+ flushCode();
1129
+ flushParagraph();
1130
+ flushList();
1131
+ if (!root.childNodes.length) {
1132
+ root.appendChild(createElement("p", "", "No preview available."));
1133
+ }
1134
+ container.appendChild(root);
1135
+ }
1136
+
1137
+ function renderRawMarkdown(container, item) {
1138
+ container.appendChild(createElement("pre", "pinokio-draft-raw", String((item && item.markdown) || "")));
1139
+ }
1140
+
1141
+ function renderMediaList(container, item) {
1142
+ const mediaItems = getMediaItems(item);
1143
+ const list = createElement("div", "pinokio-draft-media-list");
1144
+ if (!mediaItems.length) {
1145
+ list.appendChild(createElement("div", "pinokio-draft-media-item", "No media files referenced from this draft."));
1146
+ container.appendChild(list);
1147
+ return;
1148
+ }
1149
+ mediaItems.forEach((media) => {
1150
+ const row = createElement("div", "pinokio-draft-media-item");
1151
+ const icon = createElement("div", "pinokio-draft-media-item-icon");
1152
+ const kind = mediaKind(media);
1153
+ icon.appendChild(createIcon(kind === "video"
1154
+ ? "fa-solid fa-video"
1155
+ : (kind === "audio" ? "fa-solid fa-volume-high" : (kind === "image" ? "fa-solid fa-image" : "fa-solid fa-file"))));
1156
+ const copy = createElement("div", "pinokio-draft-media-item-copy");
1157
+ copy.appendChild(createElement("div", "pinokio-draft-media-item-title", media.ref || "Media"));
1158
+ const size = formatBytes(media.bytes);
1159
+ copy.appendChild(createElement("div", "pinokio-draft-media-item-meta", [media.exists ? "Ready" : "Missing", size].filter(Boolean).join(" / ")));
1160
+ row.appendChild(icon);
1161
+ row.appendChild(copy);
1162
+ if (media.exists) {
1163
+ const link = createActionButton("pinokio-draft-button secondary", "Open", "fa-solid fa-up-right-from-square");
1164
+ link.type = "button";
1165
+ link.addEventListener("click", () => {
1166
+ window.open(getMediaUrl(item, media), "_blank");
1167
+ });
1168
+ row.appendChild(link);
1169
+ }
1170
+ list.appendChild(row);
1171
+ });
1172
+ container.appendChild(list);
1173
+ }
1174
+
1175
+ function getApiUrl() {
1176
+ const url = new URL("/drafts", window.location.origin);
1177
+ if (activeCwd) {
1178
+ url.searchParams.set("cwd", activeCwd);
1179
+ }
1180
+ return url.toString();
1181
+ }
1182
+
1183
+ async function dismissItem(item) {
1184
+ const id = item && typeof item === "object" ? item.id : item;
1185
+ const revision = item && typeof item === "object" ? item.revision : "";
1186
+ if (!id) {
1187
+ return;
1188
+ }
1189
+ state.items = state.items.filter((item) => item.id !== id);
1190
+ state.expanded.delete(id);
1191
+ render();
1192
+ await fetch(`/drafts/${encodeURIComponent(id)}/dismiss`, {
1193
+ method: "POST",
1194
+ headers: {
1195
+ "Content-Type": "application/json"
1196
+ },
1197
+ body: JSON.stringify({
1198
+ revision: revision || ""
1199
+ })
1200
+ }).catch(() => {});
1201
+ }
1202
+
1203
+ async function openDraft(item, button) {
1204
+ if (!item || !item.postPath) {
1205
+ return;
1206
+ }
1207
+ const originalTextNode = button ? button.querySelector("[data-pinokio-draft-label]") : null;
1208
+ const originalText = originalTextNode ? originalTextNode.textContent : (button ? button.textContent : "");
1209
+ if (button) {
1210
+ button.disabled = true;
1211
+ setActionButtonLabel(button, "Opening...");
1212
+ }
1213
+ try {
1214
+ await fetch("/openfs", {
1215
+ method: "POST",
1216
+ headers: {
1217
+ "Content-Type": "application/json"
1218
+ },
1219
+ body: JSON.stringify({
1220
+ path: item.postPath
1221
+ })
1222
+ });
1223
+ } finally {
1224
+ if (button) {
1225
+ button.disabled = false;
1226
+ setActionButtonLabel(button, originalText || "Open draft");
1227
+ }
1228
+ }
1229
+ }
1230
+
1231
+ function writeRegistryPopup(popup, message) {
1232
+ try {
1233
+ if (!popup || popup.closed || !popup.document) {
1234
+ return;
1235
+ }
1236
+ popup.document.title = "Pinokio draft import";
1237
+ popup.document.body.innerHTML = `<div style="font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;padding:24px;color:#111827;">${String(message || "").replace(/[&<>"]/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[ch]))}</div>`;
1238
+ } catch (_) {
1239
+ }
1240
+ }
1241
+
1242
+ async function completeRegistryDraftImport(pending, payload) {
1243
+ const response = await fetch("/registry/draft-import/complete", {
1244
+ method: "POST",
1245
+ headers: {
1246
+ "Content-Type": "application/json"
1247
+ },
1248
+ body: JSON.stringify({
1249
+ draft: pending.draftId,
1250
+ token: payload.token,
1251
+ registry: payload.registry,
1252
+ app: payload.app || ""
1253
+ })
1254
+ });
1255
+ const data = await response.json().catch(() => null);
1256
+ if (!response.ok || !data || !data.editUrl) {
1257
+ const detail = data && data.status ? ` (${data.status})` : "";
1258
+ throw new Error(((data && data.error) || "Import failed.") + detail);
1259
+ }
1260
+ if (pending.popup && !pending.popup.closed) {
1261
+ pending.popup.postMessage({
1262
+ type: "pinokio:draft-import-result",
1263
+ ok: true,
1264
+ editUrl: data.editUrl
1265
+ }, pending.registryOrigin);
1266
+ } else {
1267
+ window.location.href = data.editUrl;
1268
+ }
1269
+ }
1270
+
1271
+ window.addEventListener("message", (event) => {
1272
+ const payload = event.data || {};
1273
+ if (!payload || payload.type !== "pinokio:draft-import-token" || !payload.token || !payload.registry) {
1274
+ return;
1275
+ }
1276
+ const pending = pendingRegistryImport;
1277
+ if (!pending || event.origin !== pending.registryOrigin) {
1278
+ return;
1279
+ }
1280
+ pendingRegistryImport = null;
1281
+ void completeRegistryDraftImport(pending, payload).catch((error) => {
1282
+ const message = error && error.message ? error.message : "Import failed.";
1283
+ if (pending.popup && !pending.popup.closed) {
1284
+ pending.popup.postMessage({
1285
+ type: "pinokio:draft-import-result",
1286
+ ok: false,
1287
+ error: message
1288
+ }, pending.registryOrigin);
1289
+ }
1290
+ });
1291
+ });
1292
+
1293
+ function closeDraftDrawer() {
1294
+ state.drawerItemId = "";
1295
+ const existing = document.getElementById("pinokio-draft-drawer-root");
1296
+ if (existing) {
1297
+ existing.remove();
1298
+ }
1299
+ }
1300
+
1301
+ function openDraftDrawer(item, tab) {
1302
+ if (!item || !item.id) {
1303
+ return;
1304
+ }
1305
+ state.drawerItemId = item.id;
1306
+ state.drawerTab = tab || "preview";
1307
+ renderDrawer();
1308
+ }
1309
+
1310
+ function createDrawerTab(label, tab) {
1311
+ const button = createElement("button", `pinokio-draft-tab${state.drawerTab === tab ? " is-active" : ""}`, label);
1312
+ button.type = "button";
1313
+ button.setAttribute("aria-selected", state.drawerTab === tab ? "true" : "false");
1314
+ button.addEventListener("click", () => {
1315
+ state.drawerTab = tab;
1316
+ renderDrawer();
1317
+ });
1318
+ return button;
1319
+ }
1320
+
1321
+ function renderDrawer() {
1322
+ const previous = document.getElementById("pinokio-draft-drawer-root");
1323
+ if (previous) {
1324
+ previous.remove();
1325
+ }
1326
+ const item = getItemById(state.drawerItemId);
1327
+ if (!item) {
1328
+ state.drawerItemId = "";
1329
+ return;
1330
+ }
1331
+
1332
+ const backdrop = createElement("div", "pinokio-draft-drawer-backdrop");
1333
+ backdrop.id = "pinokio-draft-drawer-root";
1334
+ backdrop.addEventListener("click", (event) => {
1335
+ if (event.target === backdrop) {
1336
+ closeDraftDrawer();
1337
+ }
1338
+ });
1339
+
1340
+ const drawer = createElement("section", "pinokio-draft-drawer");
1341
+ drawer.setAttribute("role", "dialog");
1342
+ drawer.setAttribute("aria-modal", "true");
1343
+ drawer.setAttribute("aria-label", "Draft preview");
1344
+
1345
+ const header = createElement("div", "pinokio-draft-drawer-header");
1346
+ const titleBlock = createElement("div", "pinokio-draft-drawer-title-block");
1347
+ titleBlock.appendChild(createDraftStatus("pinokio-draft-drawer-kicker"));
1348
+ titleBlock.appendChild(createElement("div", "pinokio-draft-drawer-title", item.title || "Draft"));
1349
+ titleBlock.appendChild(createElement("div", "pinokio-draft-drawer-meta", getDraftMetaParts(item).join(" / ")));
1350
+ const close = createElement("button", "pinokio-draft-drawer-close", "x");
1351
+ close.type = "button";
1352
+ close.setAttribute("aria-label", "Close draft preview");
1353
+ close.addEventListener("click", closeDraftDrawer);
1354
+ header.appendChild(titleBlock);
1355
+ header.appendChild(close);
1356
+
1357
+ const toolbar = createElement("div", "pinokio-draft-drawer-toolbar");
1358
+ const tabs = createElement("div", "pinokio-draft-tabs");
1359
+ tabs.setAttribute("role", "tablist");
1360
+ tabs.appendChild(createDrawerTab("Preview", "preview"));
1361
+ tabs.appendChild(createDrawerTab("Markdown", "markdown"));
1362
+ tabs.appendChild(createDrawerTab("Media", "media"));
1363
+ const actions = createElement("div", "pinokio-draft-drawer-actions");
1364
+ const openButton = createActionButton("pinokio-draft-button secondary", "Open draft", "fa-solid fa-folder-open");
1365
+ openButton.type = "button";
1366
+ openButton.addEventListener("click", () => {
1367
+ void openDraft(item, openButton);
1368
+ });
1369
+ actions.appendChild(openButton);
1370
+ if (canPublishToRegistry(item)) {
1371
+ const publishButton = createActionButton("pinokio-draft-button", "Publish", "fa-solid fa-arrow-up-from-bracket");
1372
+ publishButton.type = "button";
1373
+ publishButton.addEventListener("click", () => {
1374
+ void openRegistryDraftImport(item);
1375
+ });
1376
+ actions.appendChild(publishButton);
1377
+ }
1378
+ toolbar.appendChild(tabs);
1379
+ toolbar.appendChild(actions);
1380
+
1381
+ const body = createElement("div", "pinokio-draft-drawer-body");
1382
+ if (state.drawerTab === "markdown") {
1383
+ renderRawMarkdown(body, item);
1384
+ } else if (state.drawerTab === "media") {
1385
+ renderMediaList(body, item);
1386
+ } else {
1387
+ renderMarkdownPreview(body, item);
1388
+ }
1389
+
1390
+ drawer.appendChild(header);
1391
+ drawer.appendChild(toolbar);
1392
+ drawer.appendChild(body);
1393
+ backdrop.appendChild(drawer);
1394
+ document.body.appendChild(backdrop);
1395
+ }
1396
+
1397
+ async function openRegistryDraftImport(item) {
1398
+ if (!item || !item.id || !canPublishToRegistry(item)) {
1399
+ return;
1400
+ }
1401
+ const popup = window.open("about:blank", "_blank");
1402
+ if (!popup) {
1403
+ const fallback = new URL("/registry/draft-import/start", window.location.origin);
1404
+ fallback.searchParams.set("draft", item.id);
1405
+ window.location.href = fallback.toString();
1406
+ return;
1407
+ }
1408
+ writeRegistryPopup(popup, "Opening registry...");
1409
+ try {
1410
+ const url = new URL("/registry/draft-import/authorize-url", window.location.origin);
1411
+ url.searchParams.set("draft", item.id);
1412
+ url.searchParams.set("_", String(Date.now()));
1413
+ const response = await fetch(url.toString(), {
1414
+ headers: {
1415
+ Accept: "application/json"
1416
+ }
1417
+ });
1418
+ const data = await response.json().catch(() => null);
1419
+ if (!response.ok || !data || !data.authorizeUrl || !data.registryOrigin) {
1420
+ throw new Error((data && data.error) || "Unable to start registry import.");
1421
+ }
1422
+ pendingRegistryImport = {
1423
+ draftId: data.draftId || item.id,
1424
+ registryOrigin: data.registryOrigin,
1425
+ popup
1426
+ };
1427
+ popup.location.href = data.authorizeUrl;
1428
+ } catch (error) {
1429
+ writeRegistryPopup(popup, error && error.message ? error.message : "Unable to start registry import.");
1430
+ }
1431
+ }
1432
+
1433
+ function renderItem(item) {
1434
+ const card = createElement("div", "pinokio-draft-card");
1435
+ card.tabIndex = 0;
1436
+ card.setAttribute("role", "button");
1437
+ card.setAttribute("aria-label", `Open draft preview for ${item.title || "Draft"}`);
1438
+ card.addEventListener("click", () => {
1439
+ openDraftDrawer(item, "preview");
1440
+ });
1441
+ card.addEventListener("keydown", (event) => {
1442
+ if (event.key === "Enter" || event.key === " ") {
1443
+ event.preventDefault();
1444
+ openDraftDrawer(item, "preview");
1445
+ }
1446
+ });
1447
+
1448
+ const head = createElement("div", "pinokio-draft-head");
1449
+ const iconWrap = createElement("div", "pinokio-draft-icon");
1450
+ iconWrap.appendChild(createIcon("fa-solid fa-file-lines"));
1451
+
1452
+ const copy = createElement("div", "pinokio-draft-copy");
1453
+ copy.appendChild(createDraftStatus("pinokio-draft-kicker"));
1454
+ copy.appendChild(createElement("div", "pinokio-draft-title", item.title || "Draft"));
1455
+
1456
+ const meta = createElement("div", "pinokio-draft-meta");
1457
+ meta.textContent = getDraftMetaParts(item).join(" / ");
1458
+ copy.appendChild(meta);
1459
+
1460
+ const close = createElement("button", "pinokio-draft-close", "x");
1461
+ close.type = "button";
1462
+ close.title = "Dismiss";
1463
+ close.setAttribute("aria-label", "Dismiss draft");
1464
+ close.addEventListener("click", (event) => {
1465
+ event.stopPropagation();
1466
+ void dismissItem(item);
1467
+ });
1468
+ close.addEventListener("keydown", (event) => {
1469
+ event.stopPropagation();
1470
+ });
1471
+
1472
+ head.appendChild(iconWrap);
1473
+ head.appendChild(copy);
1474
+ head.appendChild(close);
1475
+ card.appendChild(head);
1476
+
1477
+ return card;
1478
+ }
1479
+
1480
+ function render() {
1481
+ const items = Array.isArray(state.items) ? state.items : [];
1482
+ if (items.length === 0) {
1483
+ removeRoot();
1484
+ closeDraftDrawer();
1485
+ state.lastSignature = "";
1486
+ return;
1487
+ }
1488
+ const root = getRoot();
1489
+ root.innerHTML = "";
1490
+ items.slice(0, 3).forEach((item) => {
1491
+ root.appendChild(renderItem(item));
1492
+ });
1493
+ if (items.length > 3) {
1494
+ root.appendChild(createElement("div", "pinokio-draft-more", `${items.length - 3} more drafts`));
1495
+ }
1496
+ if (state.drawerItemId) {
1497
+ renderDrawer();
1498
+ }
1499
+ }
1500
+
1501
+ async function refresh() {
1502
+ try {
1503
+ const response = await fetch(getApiUrl(), {
1504
+ headers: {
1505
+ Accept: "application/json"
1506
+ }
1507
+ });
1508
+ if (!response.ok) {
1509
+ return;
1510
+ }
1511
+ const payload = await response.json();
1512
+ const items = payload && Array.isArray(payload.items) ? payload.items : [];
1513
+ const signature = items.map((item) => `${item.id}:${item.revision || item.updatedAt}`).join("|");
1514
+ if (signature === state.lastSignature) {
1515
+ state.initialRefreshComplete = true;
1516
+ return;
1517
+ }
1518
+ const previousIds = new Set((Array.isArray(state.items) ? state.items : []).map((item) => item && item.id).filter(Boolean));
1519
+ const newItems = state.initialRefreshComplete
1520
+ ? items.filter((item) => item && item.id && !previousIds.has(item.id))
1521
+ : [];
1522
+ state.lastSignature = signature;
1523
+ state.items = items;
1524
+ render();
1525
+ newItems.forEach(notifyDraftReady);
1526
+ state.initialRefreshComplete = true;
1527
+ } catch (_) {
1528
+ }
1529
+ }
1530
+
1531
+ function start() {
1532
+ if (!document.body) {
1533
+ window.addEventListener("DOMContentLoaded", start, { once: true });
1534
+ return;
1535
+ }
1536
+ void refresh();
1537
+ window.setInterval(refresh, 5000);
1538
+ document.addEventListener("visibilitychange", () => {
1539
+ if (!document.hidden) {
1540
+ void refresh();
1541
+ }
1542
+ });
1543
+ }
1544
+
1545
+ start();
1546
+ })();