pinokiod 7.2.7 → 7.2.8
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/kernel/api/index.js +2 -0
- package/kernel/api/shell_run_template.js +273 -0
- package/kernel/shell.js +21 -2
- package/package.json +1 -1
- package/server/index.js +65 -5
- package/server/lib/drafts.js +376 -0
- package/server/lib/workspace_catalog.js +151 -0
- package/server/lib/workspace_runtime.js +390 -0
- package/server/public/common.js +8 -0
- package/server/public/drafts.js +632 -0
- package/server/routes/draft_import.js +469 -0
- package/server/routes/workspaces.js +44 -0
- package/server/socket.js +22 -11
- package/server/views/app.ejs +13 -0
- package/server/views/partials/main_sidebar.ejs +1 -0
- package/server/views/partials/workspace_row.ejs +61 -0
- package/server/views/terminal.ejs +6 -0
- package/server/views/terminals.ejs +1 -0
- package/server/views/workspaces.ejs +812 -0
|
@@ -0,0 +1,632 @@
|
|
|
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
|
+
};
|
|
23
|
+
let pendingRegistryImport = null;
|
|
24
|
+
|
|
25
|
+
function resolveNotificationSound() {
|
|
26
|
+
try {
|
|
27
|
+
const raw = localStorage.getItem(SOUND_PREF_STORAGE_KEY);
|
|
28
|
+
const parsed = raw ? JSON.parse(raw) : null;
|
|
29
|
+
const choice = parsed && typeof parsed.choice === "string" ? parsed.choice.trim() : "";
|
|
30
|
+
if (choice === SOUND_SILENT_CHOICE) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const withLeading = choice.startsWith("/") ? choice : `/${choice}`;
|
|
34
|
+
const decoded = decodeURIComponent(withLeading);
|
|
35
|
+
if (decoded.startsWith("/sound/") && !decoded.includes("..")) {
|
|
36
|
+
return withLeading;
|
|
37
|
+
}
|
|
38
|
+
} catch (_) {}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function claimDraftNotification(id) {
|
|
43
|
+
const normalized = typeof id === "string" ? id.trim() : "";
|
|
44
|
+
if (!normalized) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (state.notifiedIds.has(normalized)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
state.notifiedIds.add(normalized);
|
|
51
|
+
try {
|
|
52
|
+
const key = `${DRAFT_NOTIFIED_PREFIX}${normalized}`;
|
|
53
|
+
if (localStorage.getItem(key)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const token = `${Date.now()}:${Math.random().toString(36).slice(2)}`;
|
|
57
|
+
localStorage.setItem(key, token);
|
|
58
|
+
return localStorage.getItem(key) === token;
|
|
59
|
+
} catch (_) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function notifyDraftReady(item) {
|
|
65
|
+
if (!item || !claimDraftNotification(item.id)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const title = typeof item.title === "string" && item.title.trim() ? item.title.trim() : "Draft";
|
|
69
|
+
const workspaceName = typeof item.workspaceName === "string" && item.workspaceName.trim() ? item.workspaceName.trim() : "";
|
|
70
|
+
const message = workspaceName ? `${workspaceName}: ${title}` : `Draft ready: ${title}`;
|
|
71
|
+
const sound = resolveNotificationSound();
|
|
72
|
+
const playedInline = typeof window.PinokioPlayNotificationSound === "function"
|
|
73
|
+
? window.PinokioPlayNotificationSound(sound)
|
|
74
|
+
: false;
|
|
75
|
+
const payload = {
|
|
76
|
+
title: "Pinokio",
|
|
77
|
+
message,
|
|
78
|
+
timeout: 60,
|
|
79
|
+
sound: playedInline ? false : sound,
|
|
80
|
+
audience: "device",
|
|
81
|
+
device_id: (typeof window.PinokioGetDeviceId === "function") ? window.PinokioGetDeviceId() : undefined
|
|
82
|
+
};
|
|
83
|
+
try {
|
|
84
|
+
fetch(PUSH_ENDPOINT, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: {
|
|
87
|
+
"Content-Type": "application/json"
|
|
88
|
+
},
|
|
89
|
+
credentials: "include",
|
|
90
|
+
body: JSON.stringify(payload)
|
|
91
|
+
}).catch(() => {});
|
|
92
|
+
} catch (_) {
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function ensureStyles() {
|
|
97
|
+
if (document.getElementById("pinokio-drafts-style")) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const style = document.createElement("style");
|
|
101
|
+
style.id = "pinokio-drafts-style";
|
|
102
|
+
style.textContent = `
|
|
103
|
+
.pinokio-drafts {
|
|
104
|
+
position: fixed;
|
|
105
|
+
right: 16px;
|
|
106
|
+
bottom: 16px;
|
|
107
|
+
z-index: 2147483000;
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
gap: 10px;
|
|
111
|
+
width: min(390px, calc(100vw - 24px));
|
|
112
|
+
pointer-events: none;
|
|
113
|
+
color: #f8fafc;
|
|
114
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
115
|
+
}
|
|
116
|
+
.pinokio-draft-card {
|
|
117
|
+
border: 1px solid rgba(148, 163, 184, 0.35);
|
|
118
|
+
border-radius: 8px;
|
|
119
|
+
background: rgba(15, 23, 42, 0.96);
|
|
120
|
+
box-shadow: 0 16px 42px rgba(2, 6, 23, 0.4);
|
|
121
|
+
overflow: hidden;
|
|
122
|
+
pointer-events: auto;
|
|
123
|
+
}
|
|
124
|
+
.pinokio-draft-head {
|
|
125
|
+
display: flex;
|
|
126
|
+
gap: 10px;
|
|
127
|
+
align-items: flex-start;
|
|
128
|
+
padding: 12px 12px 10px;
|
|
129
|
+
}
|
|
130
|
+
.pinokio-draft-icon {
|
|
131
|
+
display: grid;
|
|
132
|
+
place-items: center;
|
|
133
|
+
flex: 0 0 auto;
|
|
134
|
+
width: 30px;
|
|
135
|
+
height: 30px;
|
|
136
|
+
border-radius: 7px;
|
|
137
|
+
background: #0f766e;
|
|
138
|
+
color: white;
|
|
139
|
+
font-size: 14px;
|
|
140
|
+
}
|
|
141
|
+
.pinokio-draft-copy {
|
|
142
|
+
min-width: 0;
|
|
143
|
+
flex: 1 1 auto;
|
|
144
|
+
}
|
|
145
|
+
.pinokio-draft-kicker {
|
|
146
|
+
color: #99f6e4;
|
|
147
|
+
font-size: 11px;
|
|
148
|
+
font-weight: 700;
|
|
149
|
+
letter-spacing: 0;
|
|
150
|
+
text-transform: uppercase;
|
|
151
|
+
line-height: 1.2;
|
|
152
|
+
margin-bottom: 3px;
|
|
153
|
+
}
|
|
154
|
+
.pinokio-draft-title {
|
|
155
|
+
color: #f8fafc;
|
|
156
|
+
font-size: 14px;
|
|
157
|
+
font-weight: 700;
|
|
158
|
+
line-height: 1.25;
|
|
159
|
+
overflow-wrap: anywhere;
|
|
160
|
+
}
|
|
161
|
+
.pinokio-draft-meta {
|
|
162
|
+
display: flex;
|
|
163
|
+
flex-wrap: wrap;
|
|
164
|
+
gap: 4px 8px;
|
|
165
|
+
margin-top: 7px;
|
|
166
|
+
color: #cbd5e1;
|
|
167
|
+
font-size: 12px;
|
|
168
|
+
line-height: 1.25;
|
|
169
|
+
}
|
|
170
|
+
.pinokio-draft-close {
|
|
171
|
+
flex: 0 0 auto;
|
|
172
|
+
width: 28px;
|
|
173
|
+
height: 28px;
|
|
174
|
+
border: 0;
|
|
175
|
+
border-radius: 6px;
|
|
176
|
+
background: transparent;
|
|
177
|
+
color: #cbd5e1;
|
|
178
|
+
cursor: pointer;
|
|
179
|
+
font-size: 16px;
|
|
180
|
+
line-height: 1;
|
|
181
|
+
}
|
|
182
|
+
.pinokio-draft-close:hover,
|
|
183
|
+
.pinokio-draft-close:focus {
|
|
184
|
+
background: rgba(148, 163, 184, 0.16);
|
|
185
|
+
color: #ffffff;
|
|
186
|
+
}
|
|
187
|
+
.pinokio-draft-preview {
|
|
188
|
+
border-top: 1px solid rgba(148, 163, 184, 0.24);
|
|
189
|
+
padding: 10px 12px 0;
|
|
190
|
+
color: #e2e8f0;
|
|
191
|
+
font-size: 12px;
|
|
192
|
+
line-height: 1.45;
|
|
193
|
+
}
|
|
194
|
+
.pinokio-draft-path {
|
|
195
|
+
margin-top: 8px;
|
|
196
|
+
color: #94a3b8;
|
|
197
|
+
font-family: "SFMono-Regular", Consolas, monospace;
|
|
198
|
+
font-size: 11px;
|
|
199
|
+
line-height: 1.35;
|
|
200
|
+
overflow-wrap: anywhere;
|
|
201
|
+
}
|
|
202
|
+
.pinokio-draft-actions {
|
|
203
|
+
display: flex;
|
|
204
|
+
align-items: center;
|
|
205
|
+
gap: 8px;
|
|
206
|
+
padding: 10px 12px 12px;
|
|
207
|
+
}
|
|
208
|
+
.pinokio-draft-button {
|
|
209
|
+
display: inline-flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
justify-content: center;
|
|
212
|
+
gap: 6px;
|
|
213
|
+
min-height: 30px;
|
|
214
|
+
border: 1px solid rgba(148, 163, 184, 0.28);
|
|
215
|
+
border-radius: 6px;
|
|
216
|
+
background: rgba(30, 41, 59, 0.92);
|
|
217
|
+
color: #f8fafc;
|
|
218
|
+
cursor: pointer;
|
|
219
|
+
font-size: 12px;
|
|
220
|
+
font-weight: 700;
|
|
221
|
+
line-height: 1.1;
|
|
222
|
+
padding: 7px 10px;
|
|
223
|
+
white-space: nowrap;
|
|
224
|
+
}
|
|
225
|
+
.pinokio-draft-button:hover,
|
|
226
|
+
.pinokio-draft-button:focus {
|
|
227
|
+
border-color: rgba(45, 212, 191, 0.7);
|
|
228
|
+
background: rgba(15, 118, 110, 0.65);
|
|
229
|
+
}
|
|
230
|
+
.pinokio-draft-button.secondary {
|
|
231
|
+
color: #dbeafe;
|
|
232
|
+
background: transparent;
|
|
233
|
+
}
|
|
234
|
+
.pinokio-draft-more {
|
|
235
|
+
align-self: flex-end;
|
|
236
|
+
border-radius: 999px;
|
|
237
|
+
background: rgba(15, 23, 42, 0.9);
|
|
238
|
+
border: 1px solid rgba(148, 163, 184, 0.35);
|
|
239
|
+
color: #cbd5e1;
|
|
240
|
+
font-size: 12px;
|
|
241
|
+
line-height: 1.2;
|
|
242
|
+
padding: 6px 10px;
|
|
243
|
+
pointer-events: auto;
|
|
244
|
+
}
|
|
245
|
+
@media (max-width: 520px) {
|
|
246
|
+
.pinokio-drafts {
|
|
247
|
+
right: 12px;
|
|
248
|
+
bottom: 12px;
|
|
249
|
+
}
|
|
250
|
+
.pinokio-draft-actions {
|
|
251
|
+
flex-wrap: wrap;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
`;
|
|
255
|
+
document.head.appendChild(style);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function getRoot() {
|
|
259
|
+
ensureStyles();
|
|
260
|
+
let root = document.getElementById("pinokio-drafts");
|
|
261
|
+
if (!root) {
|
|
262
|
+
root = document.createElement("div");
|
|
263
|
+
root.id = "pinokio-drafts";
|
|
264
|
+
root.className = "pinokio-drafts";
|
|
265
|
+
root.setAttribute("aria-live", "polite");
|
|
266
|
+
document.body.appendChild(root);
|
|
267
|
+
}
|
|
268
|
+
return root;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function removeRoot() {
|
|
272
|
+
const root = document.getElementById("pinokio-drafts");
|
|
273
|
+
if (root) {
|
|
274
|
+
root.remove();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function createElement(tag, className, text) {
|
|
279
|
+
const element = document.createElement(tag);
|
|
280
|
+
if (className) {
|
|
281
|
+
element.className = className;
|
|
282
|
+
}
|
|
283
|
+
if (text !== undefined && text !== null) {
|
|
284
|
+
element.textContent = text;
|
|
285
|
+
}
|
|
286
|
+
return element;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function createIcon(name) {
|
|
290
|
+
const icon = createElement("i", name);
|
|
291
|
+
icon.setAttribute("aria-hidden", "true");
|
|
292
|
+
return icon;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function formatBytes(bytes) {
|
|
296
|
+
const value = Number(bytes);
|
|
297
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
298
|
+
return "";
|
|
299
|
+
}
|
|
300
|
+
if (value < 1024) {
|
|
301
|
+
return `${value} B`;
|
|
302
|
+
}
|
|
303
|
+
if (value < 1024 * 1024) {
|
|
304
|
+
return `${(value / 1024).toFixed(1)} KB`;
|
|
305
|
+
}
|
|
306
|
+
return `${(value / 1024 / 1024).toFixed(1)} MB`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function formatUpdatedAt(value) {
|
|
310
|
+
const date = new Date(value);
|
|
311
|
+
if (!Number.isFinite(date.getTime())) {
|
|
312
|
+
return "";
|
|
313
|
+
}
|
|
314
|
+
return date.toLocaleString([], {
|
|
315
|
+
month: "short",
|
|
316
|
+
day: "numeric",
|
|
317
|
+
hour: "numeric",
|
|
318
|
+
minute: "2-digit"
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function getApiUrl() {
|
|
323
|
+
const url = new URL("/drafts", window.location.origin);
|
|
324
|
+
if (activeCwd) {
|
|
325
|
+
url.searchParams.set("cwd", activeCwd);
|
|
326
|
+
}
|
|
327
|
+
return url.toString();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function dismissItem(id) {
|
|
331
|
+
if (!id) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
state.items = state.items.filter((item) => item.id !== id);
|
|
335
|
+
state.expanded.delete(id);
|
|
336
|
+
render();
|
|
337
|
+
await fetch(`/drafts/${encodeURIComponent(id)}/dismiss`, {
|
|
338
|
+
method: "POST",
|
|
339
|
+
headers: {
|
|
340
|
+
"Content-Type": "application/json"
|
|
341
|
+
},
|
|
342
|
+
body: "{}"
|
|
343
|
+
}).catch(() => {});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function openDraft(item, button) {
|
|
347
|
+
if (!item || !item.postPath) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const originalText = button ? button.textContent : "";
|
|
351
|
+
if (button) {
|
|
352
|
+
button.disabled = true;
|
|
353
|
+
button.textContent = "Opening...";
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
await fetch("/openfs", {
|
|
357
|
+
method: "POST",
|
|
358
|
+
headers: {
|
|
359
|
+
"Content-Type": "application/json"
|
|
360
|
+
},
|
|
361
|
+
body: JSON.stringify({
|
|
362
|
+
path: item.postPath
|
|
363
|
+
})
|
|
364
|
+
});
|
|
365
|
+
} finally {
|
|
366
|
+
if (button) {
|
|
367
|
+
button.disabled = false;
|
|
368
|
+
button.textContent = originalText || "Open draft";
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function writeRegistryPopup(popup, message) {
|
|
374
|
+
try {
|
|
375
|
+
if (!popup || popup.closed || !popup.document) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
popup.document.title = "Pinokio draft import";
|
|
379
|
+
popup.document.body.innerHTML = `<div style="font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;padding:24px;color:#111827;">${String(message || "").replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[ch]))}</div>`;
|
|
380
|
+
} catch (_) {
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function completeRegistryDraftImport(pending, payload) {
|
|
385
|
+
const response = await fetch("/registry/draft-import/complete", {
|
|
386
|
+
method: "POST",
|
|
387
|
+
headers: {
|
|
388
|
+
"Content-Type": "application/json"
|
|
389
|
+
},
|
|
390
|
+
body: JSON.stringify({
|
|
391
|
+
draft: pending.draftId,
|
|
392
|
+
token: payload.token,
|
|
393
|
+
registry: payload.registry,
|
|
394
|
+
app: payload.app || ""
|
|
395
|
+
})
|
|
396
|
+
});
|
|
397
|
+
const data = await response.json().catch(() => null);
|
|
398
|
+
if (!response.ok || !data || !data.editUrl) {
|
|
399
|
+
const detail = data && data.status ? ` (${data.status})` : "";
|
|
400
|
+
throw new Error(((data && data.error) || "Import failed.") + detail);
|
|
401
|
+
}
|
|
402
|
+
if (pending.popup && !pending.popup.closed) {
|
|
403
|
+
pending.popup.postMessage({
|
|
404
|
+
type: "pinokio:draft-import-result",
|
|
405
|
+
ok: true,
|
|
406
|
+
editUrl: data.editUrl
|
|
407
|
+
}, pending.registryOrigin);
|
|
408
|
+
} else {
|
|
409
|
+
window.location.href = data.editUrl;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
window.addEventListener("message", (event) => {
|
|
414
|
+
const payload = event.data || {};
|
|
415
|
+
if (!payload || payload.type !== "pinokio:draft-import-token" || !payload.token || !payload.registry) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const pending = pendingRegistryImport;
|
|
419
|
+
if (!pending || event.origin !== pending.registryOrigin) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
pendingRegistryImport = null;
|
|
423
|
+
void completeRegistryDraftImport(pending, payload).catch((error) => {
|
|
424
|
+
const message = error && error.message ? error.message : "Import failed.";
|
|
425
|
+
if (pending.popup && !pending.popup.closed) {
|
|
426
|
+
pending.popup.postMessage({
|
|
427
|
+
type: "pinokio:draft-import-result",
|
|
428
|
+
ok: false,
|
|
429
|
+
error: message
|
|
430
|
+
}, pending.registryOrigin);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
async function openRegistryDraftImport(item) {
|
|
436
|
+
if (!item || !item.id) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const popup = window.open("about:blank", "pinokioRegistryDraftImport", "popup,width=760,height=820");
|
|
440
|
+
if (!popup) {
|
|
441
|
+
const fallback = new URL("/registry/draft-import/start", window.location.origin);
|
|
442
|
+
fallback.searchParams.set("draft", item.id);
|
|
443
|
+
window.location.href = fallback.toString();
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
writeRegistryPopup(popup, "Opening registry...");
|
|
447
|
+
try {
|
|
448
|
+
const url = new URL("/registry/draft-import/authorize-url", window.location.origin);
|
|
449
|
+
url.searchParams.set("draft", item.id);
|
|
450
|
+
url.searchParams.set("_", String(Date.now()));
|
|
451
|
+
const response = await fetch(url.toString(), {
|
|
452
|
+
headers: {
|
|
453
|
+
Accept: "application/json"
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
const data = await response.json().catch(() => null);
|
|
457
|
+
if (!response.ok || !data || !data.authorizeUrl || !data.registryOrigin) {
|
|
458
|
+
throw new Error((data && data.error) || "Unable to start registry import.");
|
|
459
|
+
}
|
|
460
|
+
pendingRegistryImport = {
|
|
461
|
+
draftId: data.draftId || item.id,
|
|
462
|
+
registryOrigin: data.registryOrigin,
|
|
463
|
+
popup
|
|
464
|
+
};
|
|
465
|
+
popup.location.href = data.authorizeUrl;
|
|
466
|
+
} catch (error) {
|
|
467
|
+
writeRegistryPopup(popup, error && error.message ? error.message : "Unable to start registry import.");
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function renderItem(item) {
|
|
472
|
+
const card = createElement("div", "pinokio-draft-card");
|
|
473
|
+
const head = createElement("div", "pinokio-draft-head");
|
|
474
|
+
const iconWrap = createElement("div", "pinokio-draft-icon");
|
|
475
|
+
iconWrap.appendChild(createIcon("fa-solid fa-file-lines"));
|
|
476
|
+
|
|
477
|
+
const copy = createElement("div", "pinokio-draft-copy");
|
|
478
|
+
copy.appendChild(createElement("div", "pinokio-draft-kicker", "Draft ready"));
|
|
479
|
+
copy.appendChild(createElement("div", "pinokio-draft-title", item.title || "Draft"));
|
|
480
|
+
|
|
481
|
+
const meta = createElement("div", "pinokio-draft-meta");
|
|
482
|
+
const updatedAt = formatUpdatedAt(item.updatedAt);
|
|
483
|
+
const postSize = formatBytes(item.postBytes);
|
|
484
|
+
const mediaBits = [];
|
|
485
|
+
if (item.workspaceName) {
|
|
486
|
+
mediaBits.push(item.workspaceName);
|
|
487
|
+
}
|
|
488
|
+
if (updatedAt) {
|
|
489
|
+
mediaBits.push(updatedAt);
|
|
490
|
+
}
|
|
491
|
+
if (postSize) {
|
|
492
|
+
mediaBits.push(postSize);
|
|
493
|
+
}
|
|
494
|
+
const mediaCount = Number(item.mediaCount || 0);
|
|
495
|
+
if (mediaCount > 0) {
|
|
496
|
+
mediaBits.push(`${mediaCount} media ${mediaCount === 1 ? "file" : "files"}`);
|
|
497
|
+
}
|
|
498
|
+
const missingMedia = Number(item.missingMediaCount || 0);
|
|
499
|
+
if (missingMedia > 0) {
|
|
500
|
+
mediaBits.push(`${missingMedia} missing`);
|
|
501
|
+
}
|
|
502
|
+
meta.textContent = mediaBits.join(" / ");
|
|
503
|
+
copy.appendChild(meta);
|
|
504
|
+
|
|
505
|
+
const close = createElement("button", "pinokio-draft-close", "x");
|
|
506
|
+
close.type = "button";
|
|
507
|
+
close.title = "Dismiss";
|
|
508
|
+
close.setAttribute("aria-label", "Dismiss draft");
|
|
509
|
+
close.addEventListener("click", () => {
|
|
510
|
+
void dismissItem(item.id);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
head.appendChild(iconWrap);
|
|
514
|
+
head.appendChild(copy);
|
|
515
|
+
head.appendChild(close);
|
|
516
|
+
card.appendChild(head);
|
|
517
|
+
|
|
518
|
+
if (state.expanded.has(item.id)) {
|
|
519
|
+
const preview = createElement("div", "pinokio-draft-preview");
|
|
520
|
+
preview.appendChild(createElement("div", "", item.excerpt || "No preview available."));
|
|
521
|
+
if (item.postPath) {
|
|
522
|
+
preview.appendChild(createElement("div", "pinokio-draft-path", item.postPath));
|
|
523
|
+
}
|
|
524
|
+
card.appendChild(preview);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const actions = createElement("div", "pinokio-draft-actions");
|
|
528
|
+
const openButton = createElement("button", "pinokio-draft-button", "Open draft");
|
|
529
|
+
openButton.type = "button";
|
|
530
|
+
openButton.addEventListener("click", () => {
|
|
531
|
+
void openDraft(item, openButton);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
const registryButton = createElement("button", "pinokio-draft-button", "Open in registry");
|
|
535
|
+
registryButton.type = "button";
|
|
536
|
+
registryButton.addEventListener("click", () => {
|
|
537
|
+
void openRegistryDraftImport(item);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
const previewButton = createElement(
|
|
541
|
+
"button",
|
|
542
|
+
"pinokio-draft-button secondary",
|
|
543
|
+
state.expanded.has(item.id) ? "Hide preview" : "Preview"
|
|
544
|
+
);
|
|
545
|
+
previewButton.type = "button";
|
|
546
|
+
previewButton.addEventListener("click", () => {
|
|
547
|
+
if (state.expanded.has(item.id)) {
|
|
548
|
+
state.expanded.delete(item.id);
|
|
549
|
+
} else {
|
|
550
|
+
state.expanded.add(item.id);
|
|
551
|
+
}
|
|
552
|
+
render();
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
const dismissButton = createElement("button", "pinokio-draft-button secondary", "Dismiss");
|
|
556
|
+
dismissButton.type = "button";
|
|
557
|
+
dismissButton.addEventListener("click", () => {
|
|
558
|
+
void dismissItem(item.id);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
actions.appendChild(openButton);
|
|
562
|
+
actions.appendChild(registryButton);
|
|
563
|
+
actions.appendChild(previewButton);
|
|
564
|
+
actions.appendChild(dismissButton);
|
|
565
|
+
card.appendChild(actions);
|
|
566
|
+
|
|
567
|
+
return card;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function render() {
|
|
571
|
+
const items = Array.isArray(state.items) ? state.items : [];
|
|
572
|
+
if (items.length === 0) {
|
|
573
|
+
removeRoot();
|
|
574
|
+
state.lastSignature = "";
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const root = getRoot();
|
|
578
|
+
root.innerHTML = "";
|
|
579
|
+
items.slice(0, 3).forEach((item) => {
|
|
580
|
+
root.appendChild(renderItem(item));
|
|
581
|
+
});
|
|
582
|
+
if (items.length > 3) {
|
|
583
|
+
root.appendChild(createElement("div", "pinokio-draft-more", `${items.length - 3} more drafts`));
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
async function refresh() {
|
|
588
|
+
try {
|
|
589
|
+
const response = await fetch(getApiUrl(), {
|
|
590
|
+
headers: {
|
|
591
|
+
Accept: "application/json"
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
if (!response.ok) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const payload = await response.json();
|
|
598
|
+
const items = payload && Array.isArray(payload.items) ? payload.items : [];
|
|
599
|
+
const signature = items.map((item) => `${item.id}:${item.updatedAt}`).join("|");
|
|
600
|
+
if (signature === state.lastSignature) {
|
|
601
|
+
state.initialRefreshComplete = true;
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
const previousIds = new Set((Array.isArray(state.items) ? state.items : []).map((item) => item && item.id).filter(Boolean));
|
|
605
|
+
const newItems = state.initialRefreshComplete
|
|
606
|
+
? items.filter((item) => item && item.id && !previousIds.has(item.id))
|
|
607
|
+
: [];
|
|
608
|
+
state.lastSignature = signature;
|
|
609
|
+
state.items = items;
|
|
610
|
+
render();
|
|
611
|
+
newItems.forEach(notifyDraftReady);
|
|
612
|
+
state.initialRefreshComplete = true;
|
|
613
|
+
} catch (_) {
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function start() {
|
|
618
|
+
if (!document.body) {
|
|
619
|
+
window.addEventListener("DOMContentLoaded", start, { once: true });
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
void refresh();
|
|
623
|
+
window.setInterval(refresh, 5000);
|
|
624
|
+
document.addEventListener("visibilitychange", () => {
|
|
625
|
+
if (!document.hidden) {
|
|
626
|
+
void refresh();
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
start();
|
|
632
|
+
})();
|