@emdash-cms/cloudflare 0.0.1
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/dist/auth/index.d.mts +81 -0
- package/dist/auth/index.mjs +147 -0
- package/dist/cache/config.d.mts +52 -0
- package/dist/cache/config.mjs +55 -0
- package/dist/cache/runtime.d.mts +40 -0
- package/dist/cache/runtime.mjs +191 -0
- package/dist/d1-introspector-bZf0_ylK.mjs +57 -0
- package/dist/db/d1.d.mts +43 -0
- package/dist/db/d1.mjs +74 -0
- package/dist/db/do.d.mts +96 -0
- package/dist/db/do.mjs +489 -0
- package/dist/db/playground-middleware.d.mts +20 -0
- package/dist/db/playground-middleware.mjs +533 -0
- package/dist/db/playground.d.mts +39 -0
- package/dist/db/playground.mjs +26 -0
- package/dist/do-class-DY2Ba2RJ.mjs +174 -0
- package/dist/do-class-x5Xh_G62.d.mts +73 -0
- package/dist/do-dialect-BhFcRSFQ.mjs +58 -0
- package/dist/do-playground-routes-CmwFeGwJ.mjs +49 -0
- package/dist/do-types-CY0G0oyh.d.mts +14 -0
- package/dist/images-4RT9Ag8_.d.mts +76 -0
- package/dist/index.d.mts +200 -0
- package/dist/index.mjs +214 -0
- package/dist/media/images-runtime.d.mts +10 -0
- package/dist/media/images-runtime.mjs +215 -0
- package/dist/media/stream-runtime.d.mts +10 -0
- package/dist/media/stream-runtime.mjs +218 -0
- package/dist/plugins/index.d.mts +32 -0
- package/dist/plugins/index.mjs +163 -0
- package/dist/sandbox/index.d.mts +255 -0
- package/dist/sandbox/index.mjs +945 -0
- package/dist/storage/r2.d.mts +31 -0
- package/dist/storage/r2.mjs +116 -0
- package/dist/stream-DdbcvKi0.d.mts +78 -0
- package/package.json +109 -0
- package/src/auth/cloudflare-access.ts +303 -0
- package/src/auth/index.ts +16 -0
- package/src/cache/config.ts +81 -0
- package/src/cache/runtime.ts +328 -0
- package/src/cloudflare.d.ts +31 -0
- package/src/db/d1-introspector.ts +120 -0
- package/src/db/d1.ts +112 -0
- package/src/db/do-class.ts +275 -0
- package/src/db/do-dialect.ts +125 -0
- package/src/db/do-playground-routes.ts +65 -0
- package/src/db/do-preview-routes.ts +48 -0
- package/src/db/do-preview-sign.ts +100 -0
- package/src/db/do-preview.ts +268 -0
- package/src/db/do-types.ts +12 -0
- package/src/db/do.ts +62 -0
- package/src/db/playground-middleware.ts +340 -0
- package/src/db/playground-toolbar.ts +341 -0
- package/src/db/playground.ts +49 -0
- package/src/db/preview-toolbar.ts +220 -0
- package/src/index.ts +285 -0
- package/src/media/images-runtime.ts +353 -0
- package/src/media/images.ts +114 -0
- package/src/media/stream-runtime.ts +392 -0
- package/src/media/stream.ts +118 -0
- package/src/plugins/index.ts +7 -0
- package/src/plugins/vectorize-search.ts +393 -0
- package/src/sandbox/bridge.ts +1008 -0
- package/src/sandbox/index.ts +13 -0
- package/src/sandbox/runner.ts +357 -0
- package/src/sandbox/types.ts +181 -0
- package/src/sandbox/wrapper.ts +238 -0
- package/src/storage/r2.ts +200 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playground Toolbar
|
|
3
|
+
*
|
|
4
|
+
* A floating pill injected by the playground middleware into HTML responses.
|
|
5
|
+
* Shows edit toggle, time remaining, reset button, and deploy CTA.
|
|
6
|
+
* No dependencies -- plain HTML string with inline styles and a <script> tag.
|
|
7
|
+
*
|
|
8
|
+
* The edit toggle sets the emdash-edit-mode cookie, same as the normal
|
|
9
|
+
* visual editing toolbar. The data-edit-mode attribute on the toolbar div
|
|
10
|
+
* activates the hover outlines on [data-emdash-ref] elements via CSS :has().
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface PlaygroundToolbarConfig {
|
|
14
|
+
/** When the playground was created (ISO string) */
|
|
15
|
+
createdAt: string;
|
|
16
|
+
/** TTL in seconds */
|
|
17
|
+
ttl: number;
|
|
18
|
+
/** Whether edit mode is currently active */
|
|
19
|
+
editMode: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const RE_AMP = /&/g;
|
|
23
|
+
const RE_QUOT = /"/g;
|
|
24
|
+
const RE_LT = /</g;
|
|
25
|
+
const RE_GT = />/g;
|
|
26
|
+
|
|
27
|
+
export function renderPlaygroundToolbar(config: PlaygroundToolbarConfig): string {
|
|
28
|
+
const { createdAt, ttl, editMode } = config;
|
|
29
|
+
|
|
30
|
+
return `
|
|
31
|
+
<!-- EmDash Playground Toolbar -->
|
|
32
|
+
<div id="emdash-playground-toolbar" data-created-at="${escapeAttr(createdAt)}" data-ttl="${ttl}" data-edit-mode="${editMode}">
|
|
33
|
+
<div class="ec-pg-inner">
|
|
34
|
+
<span class="ec-pg-badge">Playground</span>
|
|
35
|
+
|
|
36
|
+
<div class="ec-pg-divider"></div>
|
|
37
|
+
|
|
38
|
+
<label class="ec-pg-toggle" title="Toggle visual editing">
|
|
39
|
+
<input type="checkbox" id="ec-pg-edit-toggle" ${editMode ? "checked" : ""} />
|
|
40
|
+
<span class="ec-pg-toggle-track">
|
|
41
|
+
<span class="ec-pg-toggle-thumb"></span>
|
|
42
|
+
</span>
|
|
43
|
+
<span class="ec-pg-toggle-label">Edit</span>
|
|
44
|
+
</label>
|
|
45
|
+
|
|
46
|
+
<div class="ec-pg-divider"></div>
|
|
47
|
+
|
|
48
|
+
<span class="ec-pg-status" id="ec-pg-status"></span>
|
|
49
|
+
|
|
50
|
+
<div class="ec-pg-divider"></div>
|
|
51
|
+
|
|
52
|
+
<button class="ec-pg-btn ec-pg-btn--reset" id="ec-pg-reset" title="Reset playground">
|
|
53
|
+
<svg class="ec-pg-icon" id="ec-pg-reset-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
|
|
54
|
+
</button>
|
|
55
|
+
|
|
56
|
+
<a class="ec-pg-btn ec-pg-btn--deploy" href="https://docs.emdashcms.com/getting-started" target="_blank" rel="noopener">
|
|
57
|
+
Deploy your own
|
|
58
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" 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>
|
|
59
|
+
</a>
|
|
60
|
+
|
|
61
|
+
<button class="ec-pg-btn ec-pg-close" id="ec-pg-dismiss" title="Dismiss toolbar">
|
|
62
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<style>
|
|
68
|
+
#emdash-playground-toolbar {
|
|
69
|
+
position: fixed;
|
|
70
|
+
bottom: 16px;
|
|
71
|
+
left: 50%;
|
|
72
|
+
transform: translateX(-50%);
|
|
73
|
+
z-index: 999999;
|
|
74
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
75
|
+
font-size: 13px;
|
|
76
|
+
line-height: 1;
|
|
77
|
+
-webkit-font-smoothing: antialiased;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#emdash-playground-toolbar.ec-pg-hidden {
|
|
81
|
+
display: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.ec-pg-inner {
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: 10px;
|
|
88
|
+
padding: 8px 12px 8px 16px;
|
|
89
|
+
background: #1a1a1a;
|
|
90
|
+
color: #e0e0e0;
|
|
91
|
+
border-radius: 999px;
|
|
92
|
+
box-shadow: 0 4px 24px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.08);
|
|
93
|
+
white-space: nowrap;
|
|
94
|
+
user-select: none;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.ec-pg-badge {
|
|
98
|
+
display: inline-flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
padding: 3px 8px;
|
|
101
|
+
border-radius: 999px;
|
|
102
|
+
font-size: 11px;
|
|
103
|
+
font-weight: 600;
|
|
104
|
+
letter-spacing: 0.02em;
|
|
105
|
+
text-transform: uppercase;
|
|
106
|
+
background: rgba(234,179,8,0.2);
|
|
107
|
+
color: #facc15;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.ec-pg-divider {
|
|
111
|
+
width: 1px;
|
|
112
|
+
height: 16px;
|
|
113
|
+
background: rgba(255,255,255,0.15);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Edit toggle */
|
|
117
|
+
.ec-pg-toggle {
|
|
118
|
+
display: inline-flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
gap: 6px;
|
|
121
|
+
cursor: pointer;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.ec-pg-toggle input {
|
|
125
|
+
position: absolute;
|
|
126
|
+
opacity: 0;
|
|
127
|
+
width: 0;
|
|
128
|
+
height: 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.ec-pg-toggle-track {
|
|
132
|
+
position: relative;
|
|
133
|
+
width: 28px;
|
|
134
|
+
height: 16px;
|
|
135
|
+
border-radius: 999px;
|
|
136
|
+
background: rgba(255,255,255,0.15);
|
|
137
|
+
transition: background 0.15s;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.ec-pg-toggle input:checked + .ec-pg-toggle-track {
|
|
141
|
+
background: #3b82f6;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.ec-pg-toggle-thumb {
|
|
145
|
+
position: absolute;
|
|
146
|
+
top: 2px;
|
|
147
|
+
left: 2px;
|
|
148
|
+
width: 12px;
|
|
149
|
+
height: 12px;
|
|
150
|
+
border-radius: 50%;
|
|
151
|
+
background: #fff;
|
|
152
|
+
transition: transform 0.15s;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.ec-pg-toggle input:checked + .ec-pg-toggle-track .ec-pg-toggle-thumb {
|
|
156
|
+
transform: translateX(12px);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.ec-pg-toggle-label {
|
|
160
|
+
font-size: 12px;
|
|
161
|
+
font-weight: 500;
|
|
162
|
+
color: #999;
|
|
163
|
+
transition: color 0.15s;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.ec-pg-toggle input:checked ~ .ec-pg-toggle-label {
|
|
167
|
+
color: #e0e0e0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.ec-pg-status {
|
|
171
|
+
display: inline-flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
gap: 6px;
|
|
174
|
+
font-size: 12px;
|
|
175
|
+
color: #999;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.ec-pg-status--warning {
|
|
179
|
+
color: #fbbf24;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.ec-pg-status--expired {
|
|
183
|
+
color: #f87171;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.ec-pg-btn {
|
|
187
|
+
display: inline-flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
justify-content: center;
|
|
190
|
+
background: none;
|
|
191
|
+
border: none;
|
|
192
|
+
color: #888;
|
|
193
|
+
cursor: pointer;
|
|
194
|
+
padding: 4px;
|
|
195
|
+
border-radius: 4px;
|
|
196
|
+
transition: color 0.15s, background 0.15s;
|
|
197
|
+
font-family: inherit;
|
|
198
|
+
text-decoration: none;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.ec-pg-btn:hover {
|
|
202
|
+
color: #fff;
|
|
203
|
+
background: rgba(255,255,255,0.08);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.ec-pg-btn--deploy {
|
|
207
|
+
gap: 5px;
|
|
208
|
+
padding: 5px 10px;
|
|
209
|
+
font-size: 12px;
|
|
210
|
+
font-weight: 500;
|
|
211
|
+
color: #facc15;
|
|
212
|
+
background: rgba(234,179,8,0.12);
|
|
213
|
+
border-radius: 999px;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.ec-pg-btn--deploy:hover {
|
|
217
|
+
background: rgba(234,179,8,0.22);
|
|
218
|
+
color: #fde047;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.ec-pg-icon {
|
|
222
|
+
transition: transform 0.3s;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.ec-pg-btn:disabled {
|
|
226
|
+
opacity: 0.4;
|
|
227
|
+
cursor: not-allowed;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.ec-pg-btn:disabled:hover {
|
|
231
|
+
color: #888;
|
|
232
|
+
background: none;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@keyframes ec-pg-spin {
|
|
236
|
+
to { transform: rotate(360deg); }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.ec-pg-spinning .ec-pg-icon {
|
|
240
|
+
animation: ec-pg-spin 0.8s linear infinite;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Edit mode: editable hover styles (mirrors the visual editing toolbar CSS) */
|
|
244
|
+
body:has(#emdash-playground-toolbar[data-edit-mode="true"]) [data-emdash-ref] {
|
|
245
|
+
transition: box-shadow 0.15s, background-color 0.15s;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
body:has(#emdash-playground-toolbar[data-edit-mode="true"]) [data-emdash-ref]:hover {
|
|
249
|
+
box-shadow: 0 0 0 2px rgba(59,130,246,0.5);
|
|
250
|
+
border-radius: 4px;
|
|
251
|
+
background-color: rgba(59,130,246,0.04);
|
|
252
|
+
cursor: text;
|
|
253
|
+
}
|
|
254
|
+
</style>
|
|
255
|
+
|
|
256
|
+
<script>
|
|
257
|
+
(function() {
|
|
258
|
+
var toolbar = document.getElementById("emdash-playground-toolbar");
|
|
259
|
+
var statusEl = document.getElementById("ec-pg-status");
|
|
260
|
+
var resetBtn = document.getElementById("ec-pg-reset");
|
|
261
|
+
var dismissBtn = document.getElementById("ec-pg-dismiss");
|
|
262
|
+
var editToggle = document.getElementById("ec-pg-edit-toggle");
|
|
263
|
+
if (!toolbar || !statusEl || !resetBtn || !dismissBtn || !editToggle) return;
|
|
264
|
+
|
|
265
|
+
var createdAt = toolbar.getAttribute("data-created-at");
|
|
266
|
+
var ttl = parseInt(toolbar.getAttribute("data-ttl") || "3600", 10);
|
|
267
|
+
|
|
268
|
+
function getRemaining() {
|
|
269
|
+
if (!createdAt) return 0;
|
|
270
|
+
var created = new Date(createdAt).getTime();
|
|
271
|
+
var expiresAt = created + ttl * 1000;
|
|
272
|
+
return Math.max(0, Math.floor((expiresAt - Date.now()) / 1000));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function formatRemaining(seconds) {
|
|
276
|
+
if (seconds <= 0) return "Expired";
|
|
277
|
+
var m = Math.floor(seconds / 60);
|
|
278
|
+
if (m >= 60) {
|
|
279
|
+
var h = Math.floor(m / 60);
|
|
280
|
+
m = m % 60;
|
|
281
|
+
return h + "h " + m + "m";
|
|
282
|
+
}
|
|
283
|
+
return m + "m remaining";
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function updateStatus() {
|
|
287
|
+
var remaining = getRemaining();
|
|
288
|
+
statusEl.textContent = formatRemaining(remaining);
|
|
289
|
+
if (remaining <= 0) {
|
|
290
|
+
statusEl.className = "ec-pg-status ec-pg-status--expired";
|
|
291
|
+
} else if (remaining < 300) {
|
|
292
|
+
statusEl.className = "ec-pg-status ec-pg-status--warning";
|
|
293
|
+
} else {
|
|
294
|
+
statusEl.className = "ec-pg-status";
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
updateStatus();
|
|
299
|
+
// Update every 30s -- no seconds shown so no need for frequent updates
|
|
300
|
+
var interval = setInterval(updateStatus, 30000);
|
|
301
|
+
|
|
302
|
+
// Edit mode toggle -- sets cookie and reloads
|
|
303
|
+
editToggle.addEventListener("change", function() {
|
|
304
|
+
if (editToggle.checked) {
|
|
305
|
+
document.cookie = "emdash-edit-mode=true;path=/;samesite=lax";
|
|
306
|
+
toolbar.setAttribute("data-edit-mode", "true");
|
|
307
|
+
} else {
|
|
308
|
+
document.cookie = "emdash-edit-mode=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT";
|
|
309
|
+
toolbar.setAttribute("data-edit-mode", "false");
|
|
310
|
+
}
|
|
311
|
+
if (document.startViewTransition) {
|
|
312
|
+
document.startViewTransition(function() { location.replace(location.href); });
|
|
313
|
+
} else {
|
|
314
|
+
location.replace(location.href);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
resetBtn.addEventListener("click", function() {
|
|
319
|
+
resetBtn.disabled = true;
|
|
320
|
+
resetBtn.classList.add("ec-pg-spinning");
|
|
321
|
+
statusEl.className = "ec-pg-status";
|
|
322
|
+
statusEl.textContent = "Resetting\\u2026";
|
|
323
|
+
location.href = "/_playground/reset";
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
dismissBtn.addEventListener("click", function() {
|
|
327
|
+
toolbar.classList.add("ec-pg-hidden");
|
|
328
|
+
clearInterval(interval);
|
|
329
|
+
});
|
|
330
|
+
})();
|
|
331
|
+
</script>
|
|
332
|
+
`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function escapeAttr(str: string): string {
|
|
336
|
+
return str
|
|
337
|
+
.replace(RE_AMP, "&")
|
|
338
|
+
.replace(RE_QUOT, """)
|
|
339
|
+
.replace(RE_LT, "<")
|
|
340
|
+
.replace(RE_GT, ">");
|
|
341
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable Object playground database -- RUNTIME ENTRY
|
|
3
|
+
*
|
|
4
|
+
* Provides a createDialect() that the virtual module system expects,
|
|
5
|
+
* plus re-exports the DO class and playground middleware.
|
|
6
|
+
*
|
|
7
|
+
* In playground mode, the actual DB connection is always set by the
|
|
8
|
+
* playground middleware via ALS (runWithContext). The createDialect
|
|
9
|
+
* here creates a "dummy" dialect that will be overridden per-request.
|
|
10
|
+
* If a query somehow runs without the middleware's ALS override,
|
|
11
|
+
* the dialect throws a clear error.
|
|
12
|
+
*
|
|
13
|
+
* This module imports from cloudflare:workers transitively.
|
|
14
|
+
* Do NOT import this at config time.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { Dialect } from "kysely";
|
|
18
|
+
|
|
19
|
+
import { PreviewDODialect } from "./do-dialect.js";
|
|
20
|
+
import type { PreviewDBStub } from "./do-dialect.js";
|
|
21
|
+
import type { PreviewDOConfig } from "./do-types.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create a playground DO dialect from config.
|
|
25
|
+
*
|
|
26
|
+
* Returns a dialect that throws if any query is executed outside of
|
|
27
|
+
* the playground middleware's ALS context. In normal operation, the
|
|
28
|
+
* middleware overrides this DB via runWithContext() on every request.
|
|
29
|
+
*
|
|
30
|
+
* This factory exists to satisfy the virtual module system's
|
|
31
|
+
* createDialect() contract. The EmDash runtime creates a singleton
|
|
32
|
+
* DB from it, but all actual queries go through the ALS-scoped DB.
|
|
33
|
+
*/
|
|
34
|
+
export function createDialect(_config: PreviewDOConfig): Dialect {
|
|
35
|
+
const notInitialized: PreviewDBStub = {
|
|
36
|
+
async query(): Promise<{ rows: Record<string, unknown>[] }> {
|
|
37
|
+
throw new Error(
|
|
38
|
+
"Playground database not initialized. " +
|
|
39
|
+
"Ensure the playground middleware is registered in src/middleware.ts " +
|
|
40
|
+
"and all requests go through it.",
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return new PreviewDODialect({ getStub: () => notInitialized });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { EmDashPreviewDB } from "./do-class.js";
|
|
49
|
+
export { isBlockedInPlayground } from "./do-playground-routes.js";
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview Toolbar
|
|
3
|
+
*
|
|
4
|
+
* A floating pill injected by the preview middleware into HTML responses.
|
|
5
|
+
* Shows preview status, snapshot age, reload button, and errors.
|
|
6
|
+
* No dependencies — plain HTML string with inline styles and a <script> tag.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface PreviewToolbarConfig {
|
|
10
|
+
/** When the snapshot was generated (ISO string) */
|
|
11
|
+
generatedAt?: string;
|
|
12
|
+
/** Source site URL */
|
|
13
|
+
source?: string;
|
|
14
|
+
/** Error message if snapshot failed */
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const RE_AMP = /&/g;
|
|
19
|
+
const RE_QUOT = /"/g;
|
|
20
|
+
const RE_LT = /</g;
|
|
21
|
+
const RE_GT = />/g;
|
|
22
|
+
|
|
23
|
+
export function renderPreviewToolbar(config: PreviewToolbarConfig): string {
|
|
24
|
+
const { generatedAt, source, error } = config;
|
|
25
|
+
|
|
26
|
+
const generatedAtAttr = generatedAt ? ` data-generated-at="${escapeAttr(generatedAt)}"` : "";
|
|
27
|
+
const sourceAttr = source ? ` data-source="${escapeAttr(source)}"` : "";
|
|
28
|
+
const errorAttr = error ? ` data-error="${escapeAttr(error)}"` : "";
|
|
29
|
+
|
|
30
|
+
return `
|
|
31
|
+
<!-- EmDash Preview Toolbar -->
|
|
32
|
+
<div id="emdash-preview-toolbar"${generatedAtAttr}${sourceAttr}${errorAttr}>
|
|
33
|
+
<div class="ec-ptb-inner">
|
|
34
|
+
<span class="ec-ptb-badge">Preview</span>
|
|
35
|
+
|
|
36
|
+
<div class="ec-ptb-divider"></div>
|
|
37
|
+
|
|
38
|
+
<span class="ec-ptb-status" id="ec-ptb-status"></span>
|
|
39
|
+
|
|
40
|
+
<button class="ec-ptb-btn" id="ec-ptb-reload" title="Reload snapshot">
|
|
41
|
+
<svg class="ec-ptb-icon" id="ec-ptb-reload-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
|
|
42
|
+
</button>
|
|
43
|
+
|
|
44
|
+
<button class="ec-ptb-btn ec-ptb-close" id="ec-ptb-dismiss" title="Dismiss toolbar">
|
|
45
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<style>
|
|
51
|
+
#emdash-preview-toolbar {
|
|
52
|
+
position: fixed;
|
|
53
|
+
bottom: 16px;
|
|
54
|
+
left: 50%;
|
|
55
|
+
transform: translateX(-50%);
|
|
56
|
+
z-index: 999999;
|
|
57
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
58
|
+
font-size: 13px;
|
|
59
|
+
line-height: 1;
|
|
60
|
+
-webkit-font-smoothing: antialiased;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#emdash-preview-toolbar.ec-ptb-hidden {
|
|
64
|
+
display: none;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.ec-ptb-inner {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
gap: 10px;
|
|
71
|
+
padding: 8px 12px 8px 16px;
|
|
72
|
+
background: #1a1a1a;
|
|
73
|
+
color: #e0e0e0;
|
|
74
|
+
border-radius: 999px;
|
|
75
|
+
box-shadow: 0 4px 24px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.08);
|
|
76
|
+
white-space: nowrap;
|
|
77
|
+
user-select: none;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.ec-ptb-badge {
|
|
81
|
+
display: inline-flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
padding: 3px 8px;
|
|
84
|
+
border-radius: 999px;
|
|
85
|
+
font-size: 11px;
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
letter-spacing: 0.02em;
|
|
88
|
+
text-transform: uppercase;
|
|
89
|
+
background: rgba(139,92,246,0.2);
|
|
90
|
+
color: #a78bfa;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.ec-ptb-divider {
|
|
94
|
+
width: 1px;
|
|
95
|
+
height: 16px;
|
|
96
|
+
background: rgba(255,255,255,0.15);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.ec-ptb-status {
|
|
100
|
+
display: inline-flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: 6px;
|
|
103
|
+
font-size: 12px;
|
|
104
|
+
color: #999;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.ec-ptb-status--error {
|
|
108
|
+
color: #f87171;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.ec-ptb-btn {
|
|
112
|
+
display: inline-flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
justify-content: center;
|
|
115
|
+
background: none;
|
|
116
|
+
border: none;
|
|
117
|
+
color: #888;
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
padding: 4px;
|
|
120
|
+
border-radius: 4px;
|
|
121
|
+
transition: color 0.15s, background 0.15s;
|
|
122
|
+
font-family: inherit;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.ec-ptb-btn:hover {
|
|
126
|
+
color: #fff;
|
|
127
|
+
background: rgba(255,255,255,0.08);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.ec-ptb-icon {
|
|
131
|
+
transition: transform 0.3s;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.ec-ptb-btn:disabled {
|
|
135
|
+
opacity: 0.4;
|
|
136
|
+
cursor: not-allowed;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.ec-ptb-btn:disabled:hover {
|
|
140
|
+
color: #888;
|
|
141
|
+
background: none;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@keyframes ec-ptb-spin {
|
|
145
|
+
to { transform: rotate(360deg); }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.ec-ptb-spinning .ec-ptb-icon {
|
|
149
|
+
animation: ec-ptb-spin 0.8s linear infinite;
|
|
150
|
+
}
|
|
151
|
+
</style>
|
|
152
|
+
|
|
153
|
+
<script>
|
|
154
|
+
(function() {
|
|
155
|
+
var toolbar = document.getElementById("emdash-preview-toolbar");
|
|
156
|
+
var statusEl = document.getElementById("ec-ptb-status");
|
|
157
|
+
var reloadBtn = document.getElementById("ec-ptb-reload");
|
|
158
|
+
var dismissBtn = document.getElementById("ec-ptb-dismiss");
|
|
159
|
+
if (!toolbar || !statusEl || !reloadBtn || !dismissBtn) return;
|
|
160
|
+
|
|
161
|
+
var generatedAt = toolbar.getAttribute("data-generated-at");
|
|
162
|
+
var source = toolbar.getAttribute("data-source");
|
|
163
|
+
var error = toolbar.getAttribute("data-error");
|
|
164
|
+
|
|
165
|
+
function formatAge(isoString) {
|
|
166
|
+
if (!isoString) return null;
|
|
167
|
+
var then = new Date(isoString).getTime();
|
|
168
|
+
var now = Date.now();
|
|
169
|
+
var seconds = Math.floor((now - then) / 1000);
|
|
170
|
+
if (seconds < 60) return "just now";
|
|
171
|
+
var minutes = Math.floor(seconds / 60);
|
|
172
|
+
if (minutes < 60) return minutes + "m ago";
|
|
173
|
+
var hours = Math.floor(minutes / 60);
|
|
174
|
+
if (hours < 24) return hours + "h ago";
|
|
175
|
+
return Math.floor(hours / 24) + "d ago";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function updateStatus() {
|
|
179
|
+
if (error) {
|
|
180
|
+
statusEl.className = "ec-ptb-status ec-ptb-status--error";
|
|
181
|
+
statusEl.textContent = error;
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
var age = formatAge(generatedAt);
|
|
185
|
+
statusEl.className = "ec-ptb-status";
|
|
186
|
+
statusEl.textContent = age ? "Snapshot " + age : "Preview mode";
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
updateStatus();
|
|
190
|
+
|
|
191
|
+
// Update age display every 30s
|
|
192
|
+
var ageInterval = setInterval(updateStatus, 30000);
|
|
193
|
+
|
|
194
|
+
// Reload: hit the server endpoint which clears the httpOnly session cookie
|
|
195
|
+
// and redirects back with the original signed params for a fresh snapshot.
|
|
196
|
+
reloadBtn.addEventListener("click", function() {
|
|
197
|
+
reloadBtn.disabled = true;
|
|
198
|
+
reloadBtn.classList.add("ec-ptb-spinning");
|
|
199
|
+
statusEl.className = "ec-ptb-status";
|
|
200
|
+
statusEl.textContent = "Reloading\u2026";
|
|
201
|
+
location.href = "/_preview/reload";
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Dismiss
|
|
205
|
+
dismissBtn.addEventListener("click", function() {
|
|
206
|
+
toolbar.classList.add("ec-ptb-hidden");
|
|
207
|
+
clearInterval(ageInterval);
|
|
208
|
+
});
|
|
209
|
+
})();
|
|
210
|
+
</script>
|
|
211
|
+
`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function escapeAttr(str: string): string {
|
|
215
|
+
return str
|
|
216
|
+
.replace(RE_AMP, "&")
|
|
217
|
+
.replace(RE_QUOT, """)
|
|
218
|
+
.replace(RE_LT, "<")
|
|
219
|
+
.replace(RE_GT, ">");
|
|
220
|
+
}
|