dineway 0.1.3

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.
Files changed (96) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +89 -0
  3. package/dist/adapters-BlzWJG82.d.mts +106 -0
  4. package/dist/apply-CAPvMfoU.mjs +1339 -0
  5. package/dist/astro/index.d.mts +50 -0
  6. package/dist/astro/index.mjs +1326 -0
  7. package/dist/astro/middleware/auth.d.mts +30 -0
  8. package/dist/astro/middleware/auth.mjs +708 -0
  9. package/dist/astro/middleware/redirect.d.mts +21 -0
  10. package/dist/astro/middleware/redirect.mjs +62 -0
  11. package/dist/astro/middleware/request-context.d.mts +17 -0
  12. package/dist/astro/middleware/request-context.mjs +1371 -0
  13. package/dist/astro/middleware/setup.d.mts +19 -0
  14. package/dist/astro/middleware/setup.mjs +46 -0
  15. package/dist/astro/middleware.d.mts +12 -0
  16. package/dist/astro/middleware.mjs +1716 -0
  17. package/dist/astro/types.d.mts +269 -0
  18. package/dist/astro/types.mjs +1 -0
  19. package/dist/base64-F8-DUraK.mjs +58 -0
  20. package/dist/byline-DeWCMU_i.mjs +234 -0
  21. package/dist/bylines-DyqBV9EQ.mjs +137 -0
  22. package/dist/chunk-ClPoSABd.mjs +21 -0
  23. package/dist/cli/index.d.mts +1 -0
  24. package/dist/cli/index.mjs +3987 -0
  25. package/dist/client/external-auth-headers.d.mts +38 -0
  26. package/dist/client/external-auth-headers.mjs +101 -0
  27. package/dist/client/index.d.mts +397 -0
  28. package/dist/client/index.mjs +345 -0
  29. package/dist/config-Cq8H0SfX.mjs +46 -0
  30. package/dist/connection-C9pxzuag.mjs +52 -0
  31. package/dist/content-zSgdNmnt.mjs +836 -0
  32. package/dist/db/index.d.mts +4 -0
  33. package/dist/db/index.mjs +62 -0
  34. package/dist/db/libsql.d.mts +10 -0
  35. package/dist/db/libsql.mjs +21 -0
  36. package/dist/db/postgres.d.mts +10 -0
  37. package/dist/db/postgres.mjs +29 -0
  38. package/dist/db/sqlite.d.mts +10 -0
  39. package/dist/db/sqlite.mjs +15 -0
  40. package/dist/default-WYlzADZL.mjs +80 -0
  41. package/dist/dialect-helpers-B9uSp2GJ.mjs +89 -0
  42. package/dist/error-DrxtnGPg.mjs +26 -0
  43. package/dist/index-C-jx21qs.d.mts +4771 -0
  44. package/dist/index.d.mts +16 -0
  45. package/dist/index.mjs +30 -0
  46. package/dist/load-C6FCD1FU.mjs +27 -0
  47. package/dist/loader-qKmo0wAY.mjs +446 -0
  48. package/dist/manifest-schema-CTSEyIJ3.mjs +186 -0
  49. package/dist/media/index.d.mts +25 -0
  50. package/dist/media/index.mjs +54 -0
  51. package/dist/media/local-runtime.d.mts +38 -0
  52. package/dist/media/local-runtime.mjs +132 -0
  53. package/dist/media-DMTr80Gv.mjs +199 -0
  54. package/dist/mode-BlyYtIFO.mjs +22 -0
  55. package/dist/page/index.d.mts +148 -0
  56. package/dist/page/index.mjs +419 -0
  57. package/dist/placeholder-B3knXwNc.mjs +267 -0
  58. package/dist/placeholder-bOx1xCTY.d.mts +283 -0
  59. package/dist/plugin-utils.d.mts +57 -0
  60. package/dist/plugin-utils.mjs +77 -0
  61. package/dist/plugins/adapt-sandbox-entry.d.mts +21 -0
  62. package/dist/plugins/adapt-sandbox-entry.mjs +112 -0
  63. package/dist/query-BiaPl_g2.mjs +459 -0
  64. package/dist/redirect-JPqLAbxa.mjs +328 -0
  65. package/dist/registry-DSd1GWB8.mjs +851 -0
  66. package/dist/request-context.d.mts +49 -0
  67. package/dist/request-context.mjs +42 -0
  68. package/dist/runner-B5l1JfOj.d.mts +26 -0
  69. package/dist/runner-BGUGywgG.mjs +1529 -0
  70. package/dist/runtime.d.mts +25 -0
  71. package/dist/runtime.mjs +41 -0
  72. package/dist/search-BNruJHDL.mjs +11054 -0
  73. package/dist/seed/index.d.mts +3 -0
  74. package/dist/seed/index.mjs +15 -0
  75. package/dist/seo/index.d.mts +69 -0
  76. package/dist/seo/index.mjs +69 -0
  77. package/dist/storage/local.d.mts +38 -0
  78. package/dist/storage/local.mjs +165 -0
  79. package/dist/storage/s3.d.mts +31 -0
  80. package/dist/storage/s3.mjs +174 -0
  81. package/dist/tokens-4vgYuXsZ.mjs +170 -0
  82. package/dist/transport-C5FYnid7.mjs +417 -0
  83. package/dist/transport-gIL-e43D.d.mts +41 -0
  84. package/dist/types-BawVha09.mjs +30 -0
  85. package/dist/types-BgQeVaPj.d.mts +192 -0
  86. package/dist/types-CLLdsG3g.d.mts +103 -0
  87. package/dist/types-D38djUXv.d.mts +1196 -0
  88. package/dist/types-DShnjzb6.mjs +15 -0
  89. package/dist/types-DkvMXalq.d.mts +425 -0
  90. package/dist/types-DuNbGKjF.mjs +74 -0
  91. package/dist/types-ju-_ORz7.d.mts +182 -0
  92. package/dist/validate-CXnRKfJK.mjs +327 -0
  93. package/dist/validate-CqRJb_xU.mjs +96 -0
  94. package/dist/validate-DVKJJ-M_.d.mts +377 -0
  95. package/locals.d.ts +47 -0
  96. package/package.json +313 -0
@@ -0,0 +1,1371 @@
1
+ import "../../base64-F8-DUraK.mjs";
2
+ import { runWithContext } from "../../request-context.mjs";
3
+ import { n as parseContentId, r as verifyPreviewToken } from "../../tokens-4vgYuXsZ.mjs";
4
+ import { defineMiddleware } from "astro:middleware";
5
+
6
+ //#region src/visual-editing/toolbar.ts
7
+ function renderToolbar(config) {
8
+ const { editMode, isPreview } = config;
9
+ return `
10
+ <!-- Dineway Visual Editing Toolbar -->
11
+ <div id="dineway-toolbar" data-edit-mode="${editMode}" data-preview="${isPreview}">
12
+ <div class="dineway-tb-inner">
13
+ <span class="dineway-tb-logo">Dineway</span>
14
+
15
+ <div class="dineway-tb-divider"></div>
16
+
17
+ <label class="dineway-tb-toggle" title="Toggle edit mode">
18
+ <input type="checkbox" id="dineway-edit-toggle" ${editMode ? "checked" : ""} />
19
+ <span class="dineway-tb-toggle-track">
20
+ <span class="dineway-tb-toggle-thumb"></span>
21
+ </span>
22
+ <span class="dineway-tb-toggle-label">Edit</span>
23
+ </label>
24
+
25
+ <span class="dineway-tb-status" id="dineway-tb-status"></span>
26
+
27
+ <span class="dineway-tb-save-status" id="dineway-tb-save-status"></span>
28
+
29
+ <a class="dineway-tb-admin" id="dineway-tb-admin" href="#" target="dineway-admin" style="display:none" title="Open in admin">
30
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
31
+ </a>
32
+
33
+ <button class="dineway-tb-publish" id="dineway-tb-publish" style="display:none">Publish</button>
34
+ </div>
35
+ </div>
36
+
37
+ <style>
38
+ #dineway-toolbar {
39
+ position: fixed;
40
+ bottom: 16px;
41
+ left: 50%;
42
+ transform: translateX(-50%);
43
+ z-index: 999999;
44
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
45
+ font-size: 13px;
46
+ line-height: 1;
47
+ -webkit-font-smoothing: antialiased;
48
+ }
49
+
50
+ .dineway-tb-inner {
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 10px;
54
+ padding: 8px 16px;
55
+ background: #1a1a1a;
56
+ color: #e0e0e0;
57
+ border-radius: 999px;
58
+ box-shadow: 0 4px 24px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.08);
59
+ white-space: nowrap;
60
+ user-select: none;
61
+ }
62
+
63
+ .dineway-tb-logo {
64
+ font-weight: 600;
65
+ font-size: 12px;
66
+ letter-spacing: 0.02em;
67
+ color: #fff;
68
+ opacity: 0.7;
69
+ }
70
+
71
+ .dineway-tb-divider {
72
+ width: 1px;
73
+ height: 16px;
74
+ background: rgba(255,255,255,0.15);
75
+ }
76
+
77
+ /* Toggle switch */
78
+ .dineway-tb-toggle {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 6px;
82
+ cursor: pointer;
83
+ }
84
+
85
+ .dineway-tb-toggle input {
86
+ position: absolute;
87
+ opacity: 0;
88
+ width: 0;
89
+ height: 0;
90
+ }
91
+
92
+ .dineway-tb-toggle-track {
93
+ position: relative;
94
+ width: 32px;
95
+ height: 18px;
96
+ background: #444;
97
+ border-radius: 9px;
98
+ transition: background 0.2s;
99
+ }
100
+
101
+ .dineway-tb-toggle input:checked + .dineway-tb-toggle-track {
102
+ background: #3b82f6;
103
+ }
104
+
105
+ .dineway-tb-toggle-thumb {
106
+ position: absolute;
107
+ top: 2px;
108
+ left: 2px;
109
+ width: 14px;
110
+ height: 14px;
111
+ background: #fff;
112
+ border-radius: 50%;
113
+ transition: transform 0.2s;
114
+ }
115
+
116
+ .dineway-tb-toggle input:checked + .dineway-tb-toggle-track .dineway-tb-toggle-thumb {
117
+ transform: translateX(14px);
118
+ }
119
+
120
+ .dineway-tb-toggle-label {
121
+ font-size: 12px;
122
+ color: #aaa;
123
+ }
124
+
125
+ .dineway-tb-toggle input:checked ~ .dineway-tb-toggle-label {
126
+ color: #fff;
127
+ }
128
+
129
+ /* Status area — flex for multiple badges */
130
+ .dineway-tb-status {
131
+ display: inline-flex;
132
+ gap: 6px;
133
+ align-items: center;
134
+ }
135
+
136
+ /* Badges */
137
+ .dineway-tb-badge {
138
+ display: inline-flex;
139
+ align-items: center;
140
+ padding: 3px 8px;
141
+ border-radius: 999px;
142
+ font-size: 11px;
143
+ font-weight: 600;
144
+ letter-spacing: 0.02em;
145
+ text-transform: uppercase;
146
+ }
147
+
148
+ .dineway-tb-badge--preview {
149
+ background: rgba(139,92,246,0.2);
150
+ color: #a78bfa;
151
+ }
152
+
153
+ .dineway-tb-badge--draft {
154
+ background: rgba(245,158,11,0.2);
155
+ color: #fbbf24;
156
+ }
157
+
158
+ .dineway-tb-badge--published {
159
+ background: rgba(34,197,94,0.2);
160
+ color: #4ade80;
161
+ }
162
+
163
+ .dineway-tb-badge--pending {
164
+ background: rgba(59,130,246,0.2);
165
+ color: #60a5fa;
166
+ }
167
+
168
+ .dineway-tb-badge--unsaved {
169
+ background: rgba(245,158,11,0.2);
170
+ color: #fbbf24;
171
+ }
172
+
173
+ .dineway-tb-badge--saving {
174
+ background: rgba(148,163,184,0.2);
175
+ color: #94a3b8;
176
+ }
177
+
178
+ .dineway-tb-badge--saved {
179
+ background: rgba(34,197,94,0.2);
180
+ color: #4ade80;
181
+ transition: opacity 0.3s;
182
+ }
183
+
184
+ .dineway-tb-badge--error {
185
+ background: rgba(239,68,68,0.2);
186
+ color: #f87171;
187
+ }
188
+
189
+ /* Admin link */
190
+ .dineway-tb-admin {
191
+ display: inline-flex;
192
+ align-items: center;
193
+ justify-content: center;
194
+ color: #888;
195
+ text-decoration: none;
196
+ padding: 2px;
197
+ border-radius: 4px;
198
+ transition: color 0.15s;
199
+ }
200
+
201
+ .dineway-tb-admin:hover {
202
+ color: #fff;
203
+ }
204
+
205
+ /* Publish button */
206
+ .dineway-tb-publish {
207
+ padding: 4px 12px;
208
+ background: #3b82f6;
209
+ color: #fff;
210
+ border: none;
211
+ border-radius: 999px;
212
+ font-size: 12px;
213
+ font-weight: 600;
214
+ cursor: pointer;
215
+ transition: background 0.15s;
216
+ font-family: inherit;
217
+ }
218
+
219
+ .dineway-tb-publish:hover {
220
+ background: #2563eb;
221
+ }
222
+
223
+ .dineway-tb-publish:disabled {
224
+ opacity: 0.5;
225
+ cursor: not-allowed;
226
+ }
227
+
228
+ /* Edit mode: editable hover styles — uses :has() to check toolbar state */
229
+ body:has(#dineway-toolbar[data-edit-mode="true"]) [data-dineway-ref] {
230
+ transition: box-shadow 0.15s, background-color 0.15s;
231
+ }
232
+
233
+ body:has(#dineway-toolbar[data-edit-mode="true"]) [data-dineway-ref]:hover {
234
+ box-shadow: 0 0 0 2px rgba(59,130,246,0.5);
235
+ border-radius: 4px;
236
+ background-color: rgba(59,130,246,0.04);
237
+ cursor: text;
238
+ }
239
+
240
+ /* Active editing state — override hover pencil cursor */
241
+ [data-dineway-editing] {
242
+ box-shadow: 0 0 0 2px #3b82f6 !important;
243
+ border-radius: 4px !important;
244
+ background-color: rgba(59,130,246,0.04) !important;
245
+ cursor: text !important;
246
+ }
247
+
248
+ /* Suppress browser focus ring on contenteditable and tiptap editor */
249
+ [data-dineway-editing]:focus,
250
+ [data-dineway-ref] .tiptap:focus,
251
+ [data-dineway-ref] .ProseMirror:focus {
252
+ outline: none !important;
253
+ }
254
+
255
+ /* Image editor popover */
256
+ .dineway-img-popover {
257
+ position: fixed;
258
+ z-index: 1000000;
259
+ background: #1a1a1a;
260
+ border-radius: 12px;
261
+ box-shadow: 0 8px 32px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.08);
262
+ color: #e0e0e0;
263
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
264
+ font-size: 13px;
265
+ width: 320px;
266
+ overflow: hidden;
267
+ animation: dineway-img-fadein 0.15s ease-out;
268
+ }
269
+
270
+ @keyframes dineway-img-fadein {
271
+ from { opacity: 0; transform: translateY(4px); }
272
+ to { opacity: 1; transform: translateY(0); }
273
+ }
274
+
275
+ .dineway-img-popover-header {
276
+ display: flex;
277
+ align-items: center;
278
+ justify-content: space-between;
279
+ padding: 10px 12px;
280
+ border-bottom: 1px solid rgba(255,255,255,0.08);
281
+ }
282
+
283
+ .dineway-img-popover-title {
284
+ font-weight: 600;
285
+ font-size: 12px;
286
+ text-transform: uppercase;
287
+ letter-spacing: 0.04em;
288
+ color: #999;
289
+ }
290
+
291
+ .dineway-img-popover-close {
292
+ background: none;
293
+ border: none;
294
+ color: #666;
295
+ cursor: pointer;
296
+ padding: 2px;
297
+ line-height: 1;
298
+ font-size: 16px;
299
+ border-radius: 4px;
300
+ transition: color 0.15s;
301
+ }
302
+
303
+ .dineway-img-popover-close:hover {
304
+ color: #fff;
305
+ }
306
+
307
+ .dineway-img-popover-body {
308
+ padding: 12px;
309
+ display: flex;
310
+ flex-direction: column;
311
+ gap: 10px;
312
+ }
313
+
314
+ .dineway-img-preview {
315
+ width: 100%;
316
+ max-height: 160px;
317
+ object-fit: contain;
318
+ border-radius: 6px;
319
+ background: #111;
320
+ }
321
+
322
+ .dineway-img-empty {
323
+ display: flex;
324
+ align-items: center;
325
+ justify-content: center;
326
+ height: 80px;
327
+ border: 2px dashed rgba(255,255,255,0.15);
328
+ border-radius: 6px;
329
+ color: #666;
330
+ font-size: 12px;
331
+ }
332
+
333
+ .dineway-img-field {
334
+ display: flex;
335
+ flex-direction: column;
336
+ gap: 4px;
337
+ }
338
+
339
+ .dineway-img-field label {
340
+ font-size: 11px;
341
+ font-weight: 600;
342
+ color: #888;
343
+ text-transform: uppercase;
344
+ letter-spacing: 0.04em;
345
+ }
346
+
347
+ .dineway-img-field input[type="text"] {
348
+ background: #111;
349
+ border: 1px solid rgba(255,255,255,0.12);
350
+ border-radius: 6px;
351
+ color: #e0e0e0;
352
+ padding: 6px 8px;
353
+ font-size: 13px;
354
+ font-family: inherit;
355
+ outline: none;
356
+ transition: border-color 0.15s;
357
+ }
358
+
359
+ .dineway-img-field input[type="text"]:focus {
360
+ border-color: #3b82f6;
361
+ }
362
+
363
+ .dineway-img-actions {
364
+ display: flex;
365
+ gap: 6px;
366
+ }
367
+
368
+ .dineway-img-btn {
369
+ flex: 1;
370
+ padding: 6px 10px;
371
+ border: 1px solid rgba(255,255,255,0.12);
372
+ border-radius: 6px;
373
+ background: #222;
374
+ color: #e0e0e0;
375
+ font-size: 12px;
376
+ font-family: inherit;
377
+ cursor: pointer;
378
+ transition: background 0.15s, border-color 0.15s;
379
+ text-align: center;
380
+ white-space: nowrap;
381
+ }
382
+
383
+ .dineway-img-btn:hover {
384
+ background: #333;
385
+ border-color: rgba(255,255,255,0.2);
386
+ }
387
+
388
+ .dineway-img-btn--primary {
389
+ background: #3b82f6;
390
+ border-color: #3b82f6;
391
+ color: #fff;
392
+ }
393
+
394
+ .dineway-img-btn--primary:hover {
395
+ background: #2563eb;
396
+ border-color: #2563eb;
397
+ }
398
+
399
+ .dineway-img-btn--danger {
400
+ color: #f87171;
401
+ border-color: rgba(248,113,113,0.3);
402
+ }
403
+
404
+ .dineway-img-btn--danger:hover {
405
+ background: rgba(248,113,113,0.1);
406
+ border-color: rgba(248,113,113,0.5);
407
+ }
408
+
409
+ /* Media browser within the popover */
410
+ .dineway-img-browser {
411
+ border-top: 1px solid rgba(255,255,255,0.08);
412
+ padding: 12px;
413
+ }
414
+
415
+ .dineway-img-browser-header {
416
+ display: flex;
417
+ align-items: center;
418
+ justify-content: space-between;
419
+ margin-bottom: 8px;
420
+ }
421
+
422
+ .dineway-img-browser-title {
423
+ font-size: 12px;
424
+ font-weight: 600;
425
+ color: #999;
426
+ }
427
+
428
+ .dineway-img-browser-back {
429
+ background: none;
430
+ border: none;
431
+ color: #3b82f6;
432
+ cursor: pointer;
433
+ font-size: 12px;
434
+ font-family: inherit;
435
+ padding: 2px 4px;
436
+ }
437
+
438
+ .dineway-img-browser-back:hover {
439
+ text-decoration: underline;
440
+ }
441
+
442
+ .dineway-img-grid {
443
+ display: grid;
444
+ grid-template-columns: repeat(3, 1fr);
445
+ gap: 6px;
446
+ max-height: 240px;
447
+ overflow-y: auto;
448
+ }
449
+
450
+ .dineway-img-grid-item {
451
+ aspect-ratio: 1;
452
+ border-radius: 4px;
453
+ overflow: hidden;
454
+ cursor: pointer;
455
+ border: 2px solid transparent;
456
+ transition: border-color 0.15s;
457
+ background: #111;
458
+ }
459
+
460
+ .dineway-img-grid-item:hover {
461
+ border-color: rgba(59,130,246,0.5);
462
+ }
463
+
464
+ .dineway-img-grid-item--selected {
465
+ border-color: #3b82f6;
466
+ }
467
+
468
+ .dineway-img-grid-item img {
469
+ width: 100%;
470
+ height: 100%;
471
+ object-fit: cover;
472
+ }
473
+
474
+ .dineway-img-loading {
475
+ display: flex;
476
+ align-items: center;
477
+ justify-content: center;
478
+ height: 80px;
479
+ color: #666;
480
+ font-size: 12px;
481
+ }
482
+
483
+ .dineway-img-drop {
484
+ border: 2px dashed #3b82f6;
485
+ background: rgba(59,130,246,0.05);
486
+ }
487
+
488
+ .dineway-img-uploading {
489
+ display: flex;
490
+ align-items: center;
491
+ gap: 8px;
492
+ padding: 8px 0;
493
+ color: #999;
494
+ font-size: 12px;
495
+ }
496
+
497
+ .dineway-img-popover-backdrop {
498
+ position: fixed;
499
+ inset: 0;
500
+ z-index: 999999;
501
+ }
502
+ </style>
503
+
504
+ <script>
505
+ (function() {
506
+ var toolbar = document.getElementById("dineway-toolbar");
507
+ var toggle = document.getElementById("dineway-edit-toggle");
508
+ var statusEl = document.getElementById("dineway-tb-status");
509
+ var saveStatusEl = document.getElementById("dineway-tb-save-status");
510
+ var publishBtn = document.getElementById("dineway-tb-publish");
511
+ if (!toolbar || !toggle || !statusEl || !publishBtn || !saveStatusEl) return;
512
+
513
+ var isEditMode = toolbar.getAttribute("data-edit-mode") === "true";
514
+
515
+ // CSRF-protected fetch — adds X-Dineway-Request header to all API calls
516
+ function ecFetch(url, init) {
517
+ init = init || {};
518
+ init.headers = Object.assign({ "X-Dineway-Request": "1" }, init.headers || {});
519
+ return fetch(url, init);
520
+ }
521
+
522
+ // --- Save status tracking ---
523
+ var saveState = "idle"; // idle | unsaved | saving | saved | error
524
+ var saveHideTimer = null;
525
+
526
+ function setSaveState(state) {
527
+ saveState = state;
528
+ clearTimeout(saveHideTimer);
529
+
530
+ switch (state) {
531
+ case "unsaved":
532
+ saveStatusEl.innerHTML = '<span class="dineway-tb-badge dineway-tb-badge--unsaved">Unsaved</span>';
533
+ break;
534
+ case "saving":
535
+ saveStatusEl.innerHTML = '<span class="dineway-tb-badge dineway-tb-badge--saving">Saving\u2026</span>';
536
+ break;
537
+ case "saved":
538
+ saveStatusEl.innerHTML = '<span class="dineway-tb-badge dineway-tb-badge--saved">Saved</span>';
539
+ saveHideTimer = setTimeout(function() {
540
+ saveStatusEl.innerHTML = "";
541
+ saveState = "idle";
542
+ }, 2000);
543
+ break;
544
+ case "error":
545
+ saveStatusEl.innerHTML = '<span class="dineway-tb-badge dineway-tb-badge--error">Save failed</span>';
546
+ saveHideTimer = setTimeout(function() {
547
+ saveStatusEl.innerHTML = "";
548
+ saveState = "idle";
549
+ }, 3000);
550
+ break;
551
+ default:
552
+ saveStatusEl.innerHTML = "";
553
+ }
554
+ }
555
+
556
+ // Listen for save events from inline editors (e.g. PT editor)
557
+ document.addEventListener("dineway:save", function(e) {
558
+ var detail = e.detail || {};
559
+ if (detail.state) {
560
+ setSaveState(detail.state);
561
+ }
562
+ });
563
+
564
+ document.addEventListener("dineway:content-changed", function(e) {
565
+ var detail = e.detail || {};
566
+ if (detail.collection && detail.id) {
567
+ showUnpublishedChanges(detail.collection, detail.id);
568
+ }
569
+ });
570
+
571
+ // --- Entry status ---
572
+ var entryRef = null;
573
+
574
+ function updateStatus() {
575
+ if (!isEditMode) {
576
+ statusEl.innerHTML = "";
577
+ publishBtn.style.display = "none";
578
+ return;
579
+ }
580
+
581
+ var first = document.querySelector("[data-dineway-ref]");
582
+ if (!first) {
583
+ statusEl.innerHTML = "";
584
+ publishBtn.style.display = "none";
585
+ return;
586
+ }
587
+
588
+ try {
589
+ var ref = JSON.parse(first.getAttribute("data-dineway-ref"));
590
+ entryRef = ref;
591
+ if (!ref.status) return;
592
+
593
+ // Show admin link
594
+ var adminLink = document.getElementById("dineway-tb-admin");
595
+ if (adminLink) {
596
+ adminLink.href = "/_dineway/admin/content/" + encodeURIComponent(ref.collection) + "/" + encodeURIComponent(ref.id);
597
+ adminLink.style.display = "";
598
+ }
599
+
600
+ if (ref.status === "draft") {
601
+ statusEl.innerHTML = '<span class="dineway-tb-badge dineway-tb-badge--draft">Draft</span>';
602
+ publishBtn.style.display = "";
603
+ publishBtn.onclick = function() { publish(ref.collection, ref.id); };
604
+ } else if (ref.status === "published" && ref.hasDraft) {
605
+ statusEl.innerHTML = '<span class="dineway-tb-badge dineway-tb-badge--pending">Unpublished changes</span>';
606
+ publishBtn.style.display = "";
607
+ publishBtn.onclick = function() { publish(ref.collection, ref.id); };
608
+ } else if (ref.status === "published") {
609
+ statusEl.innerHTML = '<span class="dineway-tb-badge dineway-tb-badge--published">Published</span>';
610
+ publishBtn.style.display = "none";
611
+ }
612
+ } catch (e) {
613
+ // ignore parse errors
614
+ }
615
+ }
616
+
617
+ // Publish action
618
+ function publish(collection, id) {
619
+ publishBtn.disabled = true;
620
+ publishBtn.textContent = "Publishing\u2026";
621
+
622
+ ecFetch("/_dineway/api/content/" + encodeURIComponent(collection) + "/" + encodeURIComponent(id) + "/publish", {
623
+ method: "POST",
624
+ credentials: "same-origin",
625
+ })
626
+ .then(function(res) {
627
+ if (res.ok) {
628
+ if (document.startViewTransition) {
629
+ document.startViewTransition(function() { location.reload(); });
630
+ } else {
631
+ location.reload();
632
+ }
633
+ } else {
634
+ publishBtn.disabled = false;
635
+ publishBtn.textContent = "Publish";
636
+ console.error("Publish failed:", res.status);
637
+ }
638
+ })
639
+ .catch(function(err) {
640
+ publishBtn.disabled = false;
641
+ publishBtn.textContent = "Publish";
642
+ console.error("Publish failed:", err);
643
+ });
644
+ }
645
+
646
+ // Edit mode toggle
647
+ toggle.addEventListener("change", function() {
648
+ if (toggle.checked) {
649
+ document.cookie = "dineway-edit-mode=true;path=/;samesite=lax";
650
+ } else {
651
+ document.cookie = "dineway-edit-mode=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT";
652
+ }
653
+
654
+ if (document.startViewTransition) {
655
+ document.startViewTransition(function() { location.replace(location.href); });
656
+ } else {
657
+ location.replace(location.href);
658
+ }
659
+ });
660
+
661
+ // --- Inline editing ---
662
+
663
+ // Cached manifest (fetched once on first edit click)
664
+ var manifestCache = null;
665
+ var manifestPromise = null;
666
+
667
+ function fetchManifest() {
668
+ if (manifestCache) return Promise.resolve(manifestCache);
669
+ if (manifestPromise) return manifestPromise;
670
+ manifestPromise = ecFetch("/_dineway/api/manifest", { credentials: "same-origin" })
671
+ .then(function(r) { return r.json(); })
672
+ .then(function(m) { manifestCache = m; return m; });
673
+ return manifestPromise;
674
+ }
675
+
676
+ function getFieldKind(manifest, collection, field) {
677
+ var col = manifest.collections && manifest.collections[collection];
678
+ if (!col || !col.fields) return null;
679
+ var f = col.fields[field];
680
+ return f ? f.kind : null;
681
+ }
682
+
683
+ // Load manifest early so the first click can resolve field kinds without racing the event.
684
+ if (isEditMode) {
685
+ fetchManifest();
686
+ }
687
+
688
+ // Save a single field value
689
+ function saveField(collection, id, field, value) {
690
+ setSaveState("saving");
691
+ return ecFetch("/_dineway/api/content/" + encodeURIComponent(collection) + "/" + encodeURIComponent(id), {
692
+ method: "PUT",
693
+ credentials: "same-origin",
694
+ headers: { "Content-Type": "application/json" },
695
+ body: JSON.stringify({ data: { [field]: value } }),
696
+ })
697
+ .then(function(res) {
698
+ if (res.ok) {
699
+ setSaveState("saved");
700
+ // A save creates/updates a draft — show unpublished changes
701
+ showUnpublishedChanges(collection, id);
702
+ } else {
703
+ setSaveState("error");
704
+ console.error("Save failed:", res.status);
705
+ }
706
+ })
707
+ .catch(function(err) {
708
+ setSaveState("error");
709
+ console.error("Save failed:", err);
710
+ });
711
+ }
712
+
713
+ function showUnpublishedChanges(collection, id) {
714
+ statusEl.innerHTML = '<span class="dineway-tb-badge dineway-tb-badge--pending">Unpublished changes</span>';
715
+ publishBtn.style.display = "";
716
+ publishBtn.disabled = false;
717
+ publishBtn.textContent = "Publish";
718
+ publishBtn.onclick = function() { publish(collection, id); };
719
+ }
720
+
721
+ // Plain text inline editing (contenteditable)
722
+ var currentlyEditing = null;
723
+
724
+ function startTextEdit(element, annotation) {
725
+ if (currentlyEditing === element) return;
726
+ if (currentlyEditing) endCurrentEdit();
727
+
728
+ currentlyEditing = element;
729
+ var originalText = element.textContent || "";
730
+
731
+ element.setAttribute("data-dineway-editing", "");
732
+ element.contentEditable = "plaintext-only";
733
+ element.focus();
734
+
735
+ // Select all text
736
+ var range = document.createRange();
737
+ range.selectNodeContents(element);
738
+ var sel = window.getSelection();
739
+ sel.removeAllRanges();
740
+ sel.addRange(range);
741
+
742
+ // Track dirty state via input events
743
+ function handleInput() {
744
+ var current = (element.textContent || "").trim();
745
+ if (current !== originalText.trim()) {
746
+ setSaveState("unsaved");
747
+ } else {
748
+ setSaveState("idle");
749
+ }
750
+ }
751
+
752
+ function handleBlur() {
753
+ element.removeEventListener("blur", handleBlur);
754
+ element.removeEventListener("keydown", handleKeydown);
755
+ element.removeEventListener("input", handleInput);
756
+ element.contentEditable = "false";
757
+ element.removeAttribute("data-dineway-editing");
758
+ currentlyEditing = null;
759
+
760
+ var newValue = (element.textContent || "").trim();
761
+ if (newValue !== originalText.trim()) {
762
+ saveField(annotation.collection, annotation.id, annotation.field, newValue);
763
+ } else {
764
+ setSaveState("idle");
765
+ }
766
+ }
767
+
768
+ function handleKeydown(e) {
769
+ if (e.key === "Enter" && !e.shiftKey) {
770
+ e.preventDefault();
771
+ element.blur();
772
+ }
773
+ if (e.key === "Escape") {
774
+ element.textContent = originalText;
775
+ setSaveState("idle");
776
+ element.blur();
777
+ }
778
+ }
779
+
780
+ element.addEventListener("input", handleInput);
781
+ element.addEventListener("blur", handleBlur);
782
+ element.addEventListener("keydown", handleKeydown);
783
+ }
784
+
785
+ function endCurrentEdit() {
786
+ if (currentlyEditing) {
787
+ currentlyEditing.blur();
788
+ }
789
+ }
790
+
791
+ // Fallback: open admin
792
+ function openAdmin(annotation) {
793
+ var url = "/_dineway/admin/content/" + encodeURIComponent(annotation.collection) + "/" + encodeURIComponent(annotation.id);
794
+ if (annotation.field) {
795
+ url += "?field=" + encodeURIComponent(annotation.field);
796
+ }
797
+ window.open(url, "dineway-admin");
798
+ }
799
+
800
+ // --- Inline image editing ---
801
+ var activeImagePopover = null;
802
+
803
+ function closeImagePopover() {
804
+ if (activeImagePopover) {
805
+ activeImagePopover.backdrop.remove();
806
+ activeImagePopover.popover.remove();
807
+ if (activeImagePopover.escapeHandler) {
808
+ document.removeEventListener("keydown", activeImagePopover.escapeHandler);
809
+ }
810
+ activeImagePopover = null;
811
+ }
812
+ }
813
+
814
+ function startImageEdit(element, annotation) {
815
+ closeImagePopover();
816
+
817
+ // Find the current image value by fetching the entry
818
+ var collection = annotation.collection;
819
+ var id = annotation.id;
820
+ var field = annotation.field;
821
+
822
+ // Find img element inside the annotated container (or the element itself if it's an img)
823
+ var imgEl = element.tagName === "IMG" ? element : element.querySelector("img");
824
+
825
+ // Fetch current field value from the content API
826
+ ecFetch("/_dineway/api/content/" + encodeURIComponent(collection) + "/" + encodeURIComponent(id), {
827
+ credentials: "same-origin"
828
+ })
829
+ .then(function(r) { return r.json(); })
830
+ .then(function(entry) {
831
+ var currentValue = entry.data && entry.data[field];
832
+ showImagePopover(element, imgEl, annotation, currentValue);
833
+ })
834
+ .catch(function() {
835
+ // If fetch fails, still show popover with what we can infer from DOM
836
+ showImagePopover(element, imgEl, annotation, null);
837
+ });
838
+ }
839
+
840
+ function showImagePopover(element, imgEl, annotation, currentValue) {
841
+ closeImagePopover();
842
+
843
+ var collection = annotation.collection;
844
+ var id = annotation.id;
845
+ var field = annotation.field;
846
+
847
+ // Position near the element
848
+ var rect = element.getBoundingClientRect();
849
+ var viewportH = window.innerHeight;
850
+ var viewportW = window.innerWidth;
851
+
852
+ // Create backdrop for click-outside-to-close
853
+ var backdrop = document.createElement("div");
854
+ backdrop.className = "dineway-img-popover-backdrop";
855
+ backdrop.addEventListener("click", function(e) {
856
+ if (e.target === backdrop) closeImagePopover();
857
+ });
858
+
859
+ // Create popover
860
+ var popover = document.createElement("div");
861
+ popover.className = "dineway-img-popover";
862
+
863
+ var currentSrc = currentValue ? (currentValue.previewUrl || currentValue.src) : (imgEl ? imgEl.src : null);
864
+ var currentAlt = currentValue ? (currentValue.alt || "") : (imgEl ? (imgEl.alt || "") : "");
865
+
866
+ // Build popover HTML
867
+ var html = '';
868
+ html += '<div class="dineway-img-popover-header">';
869
+ html += ' <span class="dineway-img-popover-title">Image</span>';
870
+ html += ' <button class="dineway-img-popover-close" data-action="close">&times;</button>';
871
+ html += '</div>';
872
+ html += '<div class="dineway-img-popover-body" id="dineway-img-main">';
873
+
874
+ if (currentSrc) {
875
+ html += '<img class="dineway-img-preview" src="' + escapeAttr(currentSrc) + '" alt="" />';
876
+ } else {
877
+ html += '<div class="dineway-img-empty">No image selected</div>';
878
+ }
879
+
880
+ html += '<div class="dineway-img-field">';
881
+ html += ' <label for="dineway-img-alt">Alt text</label>';
882
+ html += ' <input type="text" id="dineway-img-alt" value="' + escapeAttr(currentAlt) + '" placeholder="Describe the image" />';
883
+ html += '</div>';
884
+
885
+ html += '<div class="dineway-img-actions">';
886
+ html += ' <button class="dineway-img-btn dineway-img-btn--primary" data-action="browse">Replace</button>';
887
+ html += ' <label class="dineway-img-btn" style="cursor:pointer">';
888
+ html += ' Upload';
889
+ html += ' <input type="file" accept="image/*" id="dineway-img-upload" style="display:none" />';
890
+ html += ' </label>';
891
+ if (currentSrc) {
892
+ html += ' <button class="dineway-img-btn dineway-img-btn--danger" data-action="remove">Remove</button>';
893
+ }
894
+ html += '</div>';
895
+ html += '</div>';
896
+
897
+ popover.innerHTML = html;
898
+
899
+ backdrop.appendChild(popover);
900
+ document.body.appendChild(backdrop);
901
+
902
+ // Position the popover
903
+ positionPopover(popover, rect, viewportW, viewportH);
904
+
905
+ // Escape key handler
906
+ function handleEscape(e) {
907
+ if (e.key === "Escape") {
908
+ closeImagePopover();
909
+ document.removeEventListener("keydown", handleEscape);
910
+ }
911
+ }
912
+ document.addEventListener("keydown", handleEscape);
913
+
914
+ activeImagePopover = {
915
+ backdrop: backdrop,
916
+ popover: popover,
917
+ annotation: annotation,
918
+ currentValue: currentValue,
919
+ element: element,
920
+ imgEl: imgEl,
921
+ escapeHandler: handleEscape
922
+ };
923
+
924
+ // Event handlers
925
+ popover.querySelector('[data-action="close"]').addEventListener("click", closeImagePopover);
926
+
927
+ popover.querySelector('[data-action="browse"]').addEventListener("click", function() {
928
+ showMediaBrowser(popover, annotation, currentValue, element, imgEl);
929
+ });
930
+
931
+ var uploadInput = popover.querySelector("#dineway-img-upload");
932
+ uploadInput.addEventListener("change", function(e) {
933
+ var file = e.target.files && e.target.files[0];
934
+ if (file) handleImageUpload(file, popover, annotation, element, imgEl);
935
+ });
936
+
937
+ var removeBtn = popover.querySelector('[data-action="remove"]');
938
+ if (removeBtn) {
939
+ removeBtn.addEventListener("click", function() {
940
+ saveField(collection, id, field, null).then(function() {
941
+ if (imgEl) {
942
+ imgEl.style.display = "none";
943
+ }
944
+ closeImagePopover();
945
+ });
946
+ });
947
+ }
948
+
949
+ // Save alt text on change (debounced)
950
+ var altInput = popover.querySelector("#dineway-img-alt");
951
+ var altTimer = null;
952
+ altInput.addEventListener("input", function() {
953
+ clearTimeout(altTimer);
954
+ altTimer = setTimeout(function() {
955
+ var newAlt = altInput.value;
956
+ if (currentValue) {
957
+ var updated = Object.assign({}, currentValue, { alt: newAlt });
958
+ saveField(collection, id, field, updated);
959
+ if (imgEl) imgEl.alt = newAlt;
960
+ }
961
+ }, 500);
962
+ });
963
+
964
+ // Handle drag and drop on the popover body
965
+ var body = popover.querySelector(".dineway-img-popover-body");
966
+ body.addEventListener("dragover", function(e) {
967
+ e.preventDefault();
968
+ body.classList.add("dineway-img-drop");
969
+ });
970
+ body.addEventListener("dragleave", function() {
971
+ body.classList.remove("dineway-img-drop");
972
+ });
973
+ body.addEventListener("drop", function(e) {
974
+ e.preventDefault();
975
+ body.classList.remove("dineway-img-drop");
976
+ var file = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0];
977
+ if (file && file.type.startsWith("image/")) {
978
+ handleImageUpload(file, popover, annotation, element, imgEl);
979
+ }
980
+ });
981
+ }
982
+
983
+ function positionPopover(popover, targetRect, viewportW, viewportH) {
984
+ var popoverW = 320;
985
+ var gap = 8;
986
+
987
+ // Try to place to the right of the element
988
+ var left = targetRect.right + gap;
989
+ var top = targetRect.top;
990
+
991
+ // If it overflows right, place to the left
992
+ if (left + popoverW > viewportW - 16) {
993
+ left = targetRect.left - popoverW - gap;
994
+ }
995
+ // If it still overflows (narrow viewport), center below
996
+ if (left < 16) {
997
+ left = Math.max(16, (viewportW - popoverW) / 2);
998
+ top = targetRect.bottom + gap;
999
+ }
1000
+ // Clamp vertically
1001
+ if (top + 400 > viewportH - 80) { // 80 for toolbar
1002
+ top = Math.max(16, viewportH - 480);
1003
+ }
1004
+ if (top < 16) top = 16;
1005
+
1006
+ popover.style.left = left + "px";
1007
+ popover.style.top = top + "px";
1008
+ }
1009
+
1010
+ function escapeAttr(str) {
1011
+ return String(str || "").replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1012
+ }
1013
+
1014
+ function showMediaBrowser(popover, annotation, currentValue, element, imgEl) {
1015
+ var mainBody = popover.querySelector("#dineway-img-main");
1016
+ if (mainBody) mainBody.style.display = "none";
1017
+
1018
+ // Remove existing browser if any
1019
+ var existing = popover.querySelector(".dineway-img-browser");
1020
+ if (existing) existing.remove();
1021
+
1022
+ var browser = document.createElement("div");
1023
+ browser.className = "dineway-img-browser";
1024
+
1025
+ browser.innerHTML = '<div class="dineway-img-browser-header">' +
1026
+ '<span class="dineway-img-browser-title">Media Library</span>' +
1027
+ '<button class="dineway-img-browser-back">Back</button>' +
1028
+ '</div>' +
1029
+ '<div class="dineway-img-loading">Loading\u2026</div>';
1030
+
1031
+ popover.appendChild(browser);
1032
+
1033
+ browser.querySelector(".dineway-img-browser-back").addEventListener("click", function() {
1034
+ browser.remove();
1035
+ if (mainBody) mainBody.style.display = "";
1036
+ });
1037
+
1038
+ // Fetch media
1039
+ ecFetch("/_dineway/api/media?mimeType=image/&limit=30", { credentials: "same-origin" })
1040
+ .then(function(r) { return r.json(); })
1041
+ .then(function(data) {
1042
+ var items = data.items || [];
1043
+ var loadingEl = browser.querySelector(".dineway-img-loading");
1044
+ if (loadingEl) loadingEl.remove();
1045
+
1046
+ if (items.length === 0) {
1047
+ var empty = document.createElement("div");
1048
+ empty.className = "dineway-img-loading";
1049
+ empty.textContent = "No images found";
1050
+ browser.appendChild(empty);
1051
+ return;
1052
+ }
1053
+
1054
+ var grid = document.createElement("div");
1055
+ grid.className = "dineway-img-grid";
1056
+
1057
+ items.forEach(function(item) {
1058
+ var thumb = document.createElement("div");
1059
+ thumb.className = "dineway-img-grid-item";
1060
+ if (currentValue && currentValue.id === item.id) {
1061
+ thumb.classList.add("dineway-img-grid-item--selected");
1062
+ }
1063
+ var thumbUrl = item.url || item.previewUrl || ("/_dineway/api/media/file/" + item.storageKey);
1064
+ thumb.innerHTML = '<img src="' + escapeAttr(thumbUrl) + '" alt="' + escapeAttr(item.alt || item.filename || "") + '" loading="lazy" />';
1065
+
1066
+ thumb.addEventListener("click", function() {
1067
+ selectMediaItem(item, annotation, element, imgEl);
1068
+ });
1069
+
1070
+ grid.appendChild(thumb);
1071
+ });
1072
+
1073
+ browser.appendChild(grid);
1074
+ })
1075
+ .catch(function(err) {
1076
+ var loadingEl = browser.querySelector(".dineway-img-loading");
1077
+ if (loadingEl) loadingEl.textContent = "Failed to load media";
1078
+ console.error("Media fetch error:", err);
1079
+ });
1080
+ }
1081
+
1082
+ function selectMediaItem(item, annotation, element, imgEl) {
1083
+ var collection = annotation.collection;
1084
+ var id = annotation.id;
1085
+ var field = annotation.field;
1086
+
1087
+ var isLocal = !item.provider || item.provider === "local";
1088
+ var itemUrl = item.url || item.previewUrl || ("/_dineway/api/media/file/" + item.storageKey);
1089
+
1090
+ var newValue = {
1091
+ id: item.id,
1092
+ provider: item.provider || "local",
1093
+ src: isLocal ? itemUrl : undefined,
1094
+ previewUrl: isLocal ? undefined : itemUrl,
1095
+ alt: item.alt || "",
1096
+ width: item.width,
1097
+ height: item.height,
1098
+ meta: item.meta
1099
+ };
1100
+
1101
+ // Clean undefined fields
1102
+ Object.keys(newValue).forEach(function(k) {
1103
+ if (newValue[k] === undefined) delete newValue[k];
1104
+ });
1105
+
1106
+ saveField(collection, id, field, newValue).then(function() {
1107
+ // Update the image in the DOM
1108
+ if (imgEl) {
1109
+ imgEl.src = itemUrl;
1110
+ imgEl.alt = item.alt || "";
1111
+ imgEl.style.display = "";
1112
+ }
1113
+ closeImagePopover();
1114
+ });
1115
+ }
1116
+
1117
+ function handleImageUpload(file, popover, annotation, element, imgEl) {
1118
+ var collection = annotation.collection;
1119
+ var id = annotation.id;
1120
+ var field = annotation.field;
1121
+
1122
+ // Show uploading state
1123
+ var mainBody = popover.querySelector("#dineway-img-main");
1124
+ var browserEl = popover.querySelector(".dineway-img-browser");
1125
+ if (browserEl) browserEl.remove();
1126
+ if (mainBody) {
1127
+ mainBody.innerHTML = '<div class="dineway-img-uploading">' +
1128
+ '<span>Uploading ' + escapeAttr(file.name) + '\u2026</span>' +
1129
+ '</div>';
1130
+ mainBody.style.display = "";
1131
+ }
1132
+
1133
+ // Detect dimensions before upload
1134
+ var dimPromise = new Promise(function(resolve) {
1135
+ if (!file.type.startsWith("image/")) return resolve({});
1136
+ var img = new Image();
1137
+ img.onload = function() {
1138
+ resolve({ width: img.naturalWidth, height: img.naturalHeight });
1139
+ URL.revokeObjectURL(img.src);
1140
+ };
1141
+ img.onerror = function() {
1142
+ resolve({});
1143
+ URL.revokeObjectURL(img.src);
1144
+ };
1145
+ img.src = URL.createObjectURL(file);
1146
+ });
1147
+
1148
+ dimPromise.then(function(dims) {
1149
+ // Generate a thumbnail for large images to avoid OOM in server-side
1150
+ // blurhash generation on memory-constrained runtimes (Workers).
1151
+ // Thumbnail fits within a 64x64 box (scale by max dimension) so that
1152
+ // extreme aspect ratios don't explode into a huge canvas client-side.
1153
+ var thumbPromise;
1154
+ if (dims.width && dims.height && dims.width * dims.height * 4 > 32 * 1024 * 1024) {
1155
+ thumbPromise = new Promise(function(resolve) {
1156
+ try {
1157
+ var maxDim = Math.max(dims.width, dims.height);
1158
+ var scale = Math.min(1, 64 / maxDim);
1159
+ var thumbW = Math.max(1, Math.round(dims.width * scale));
1160
+ var thumbH = Math.max(1, Math.round(dims.height * scale));
1161
+ var canvas = document.createElement("canvas");
1162
+ canvas.width = thumbW;
1163
+ canvas.height = thumbH;
1164
+ var ctx = canvas.getContext("2d");
1165
+ if (ctx) {
1166
+ var img = new Image();
1167
+ img.onload = function() {
1168
+ try {
1169
+ ctx.drawImage(img, 0, 0, thumbW, thumbH);
1170
+ canvas.toBlob(function(blob) {
1171
+ URL.revokeObjectURL(img.src);
1172
+ resolve(blob);
1173
+ }, "image/png");
1174
+ } catch (e) {
1175
+ URL.revokeObjectURL(img.src);
1176
+ resolve(null);
1177
+ }
1178
+ };
1179
+ img.onerror = function() {
1180
+ URL.revokeObjectURL(img.src);
1181
+ resolve(null);
1182
+ };
1183
+ img.src = URL.createObjectURL(file);
1184
+ } else {
1185
+ resolve(null);
1186
+ }
1187
+ } catch (e) {
1188
+ resolve(null);
1189
+ }
1190
+ });
1191
+ } else {
1192
+ thumbPromise = Promise.resolve(null);
1193
+ }
1194
+
1195
+ return thumbPromise.then(function(thumbnail) {
1196
+ var formData = new FormData();
1197
+ formData.append("file", file);
1198
+ if (dims.width) formData.append("width", String(dims.width));
1199
+ if (dims.height) formData.append("height", String(dims.height));
1200
+ if (thumbnail) formData.append("thumbnail", thumbnail, "thumb.png");
1201
+
1202
+ return ecFetch("/_dineway/api/media", {
1203
+ method: "POST",
1204
+ credentials: "same-origin",
1205
+ body: formData
1206
+ });
1207
+ });
1208
+ })
1209
+ .then(function(r) { return r.json(); })
1210
+ .then(function(data) {
1211
+ if (!data.item) throw new Error("Upload failed");
1212
+ var item = data.item;
1213
+ selectMediaItem(item, annotation, element, imgEl);
1214
+ })
1215
+ .catch(function(err) {
1216
+ console.error("Upload error:", err);
1217
+ setSaveState("error");
1218
+ closeImagePopover();
1219
+ });
1220
+ }
1221
+
1222
+ // Click handler for edit mode
1223
+ if (isEditMode) {
1224
+ document.addEventListener("click", function(e) {
1225
+ var target = e.target;
1226
+
1227
+ // Don't intercept clicks on elements currently being edited
1228
+ if (target.hasAttribute && target.hasAttribute("data-dineway-editing")) return;
1229
+
1230
+ // Walk up to find annotated element
1231
+ while (target && target !== document.body) {
1232
+ if (target.hasAttribute && target.hasAttribute("data-dineway-editing")) return;
1233
+
1234
+ var ref = target.getAttribute && target.getAttribute("data-dineway-ref");
1235
+ if (ref) {
1236
+ try {
1237
+ var annotation = JSON.parse(ref);
1238
+
1239
+ // Entry-level annotation (no field) — keep walking for a field-level ancestor
1240
+ if (!annotation.field) {
1241
+ target = target.parentElement;
1242
+ continue;
1243
+ }
1244
+
1245
+ function dispatchInline(kind) {
1246
+ closeImagePopover();
1247
+ // Portable Text is edited in-page by InlinePortableTextEditor — do not open admin
1248
+ if (kind === "portableText") {
1249
+ return;
1250
+ }
1251
+ e.preventDefault();
1252
+ e.stopPropagation();
1253
+ if (kind === "string" || kind === "text") {
1254
+ startTextEdit(target, annotation);
1255
+ } else if (kind === "image") {
1256
+ startImageEdit(target, annotation);
1257
+ } else {
1258
+ openAdmin(annotation);
1259
+ }
1260
+ }
1261
+
1262
+ if (manifestCache) {
1263
+ dispatchInline(getFieldKind(manifestCache, annotation.collection, annotation.field));
1264
+ } else {
1265
+ fetchManifest().then(function(manifest) {
1266
+ dispatchInline(getFieldKind(manifest, annotation.collection, annotation.field));
1267
+ });
1268
+ }
1269
+ } catch (err) {
1270
+ console.error("Failed to parse Dineway ref:", err);
1271
+ }
1272
+ return;
1273
+ }
1274
+ target = target.parentElement;
1275
+ }
1276
+ }, true);
1277
+ }
1278
+
1279
+ updateStatus();
1280
+ })();
1281
+ <\/script>
1282
+ `;
1283
+ }
1284
+
1285
+ //#endregion
1286
+ //#region src/astro/middleware/request-context.ts
1287
+ /**
1288
+ * Dineway Request Context Middleware
1289
+ *
1290
+ * Sets up AsyncLocalStorage-based request context for query functions.
1291
+ * Skips ALS entirely for logged-out users with no CMS signals (fast path).
1292
+ *
1293
+ * Handles:
1294
+ * - Preview tokens: _preview query param with signed HMAC token
1295
+ * - Edit mode: dineway-edit-mode cookie (for visual editing)
1296
+ * - Toolbar injection: floating pill for authenticated editors
1297
+ */
1298
+ /**
1299
+ * Inject toolbar HTML into a response if it's an HTML page.
1300
+ * Returns the original response if not HTML.
1301
+ */
1302
+ async function injectToolbar(response, toolbarHtml) {
1303
+ if (!response.headers.get("content-type")?.includes("text/html")) return response;
1304
+ const html = await response.text();
1305
+ if (!html.includes("</body>")) return new Response(html, response);
1306
+ const injected = html.replace("</body>", `${toolbarHtml}</body>`);
1307
+ return new Response(injected, {
1308
+ status: response.status,
1309
+ headers: response.headers
1310
+ });
1311
+ }
1312
+ const onRequest = defineMiddleware(async (context, next) => {
1313
+ const { cookies, url } = context;
1314
+ if (url.pathname.startsWith("/_dineway")) return next();
1315
+ const { user } = context.locals;
1316
+ const isEditor = !!user && user.role >= 30;
1317
+ const requestDb = context.locals.__dinewayRequestDb;
1318
+ if (requestDb) return runWithContext({
1319
+ editMode: cookies.get("dineway-edit-mode")?.value === "true",
1320
+ db: requestDb
1321
+ }, () => next());
1322
+ const hasEditCookie = cookies.get("dineway-edit-mode")?.value === "true";
1323
+ const hasPreviewToken = url.searchParams.has("_preview");
1324
+ if (!hasEditCookie && !hasPreviewToken && !isEditor) return next();
1325
+ const editMode = hasEditCookie && isEditor;
1326
+ const locale = context.currentLocale;
1327
+ let preview;
1328
+ if (hasPreviewToken) {
1329
+ const secret = import.meta.env.DINEWAY_PREVIEW_SECRET || import.meta.env.PREVIEW_SECRET || "";
1330
+ if (secret) {
1331
+ const result = await verifyPreviewToken({
1332
+ url,
1333
+ secret
1334
+ });
1335
+ if (result.valid) {
1336
+ const { collection, id } = parseContentId(result.payload.cid);
1337
+ preview = {
1338
+ collection,
1339
+ id
1340
+ };
1341
+ }
1342
+ }
1343
+ }
1344
+ if (hasEditCookie || hasPreviewToken) return runWithContext({
1345
+ editMode,
1346
+ preview,
1347
+ locale
1348
+ }, async () => {
1349
+ let response = await next();
1350
+ if (preview) {
1351
+ response = new Response(response.body, response);
1352
+ response.headers.set("Cache-Control", "private, no-store");
1353
+ }
1354
+ if (isEditor) {
1355
+ const toolbarHtml = renderToolbar({
1356
+ editMode,
1357
+ isPreview: !!preview
1358
+ });
1359
+ return injectToolbar(response, toolbarHtml);
1360
+ }
1361
+ return response;
1362
+ });
1363
+ if (isEditor) return injectToolbar(await next(), renderToolbar({
1364
+ editMode: false,
1365
+ isPreview: false
1366
+ }));
1367
+ return next();
1368
+ });
1369
+
1370
+ //#endregion
1371
+ export { onRequest as default, onRequest };